int
spool_open_temp(uschar *temp_name)
{
int fd = Uopen(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(D_any) debug_printf("%s exists: unlinking\n", temp_name);
  Uunlink(temp_name);
  fd = Uopen(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, and
double-check the mode because the group setting doesn't always get set
automatically. */

if (fd >= 0)
  if (fchown(fd, exim_uid, exim_gid) || fchmod(fd, SPOOL_MODE))
    {
    DEBUG(D_any) debug_printf("failed setting perms on %s\n", temp_name);
    (void) close(fd); fd = -1;
    Uunlink(temp_name);
    }

return fd;
}
Exemple #2
0
A3(PUBLIC, OSErr, ROMlib_newresfork, char *, name, LONGINT *, fdp,
   boolean_t, unix_p)
{
    LONGINT fd;
    OSErr retval;


    if (netatalk_conventions_p)
      double_dir_op (name, mkdir_op);
    if (unix_p)
      {
	ourentries.finfo.finfo.fdType = TICKX("TEXT");
	ourentries.finfo.finfo.fdCreator = TICKX("UNIX");
      }
    else
      {
	ourentries.finfo.finfo.fdType = 0;
	ourentries.finfo.finfo.fdCreator = 0;
      }
    initialize_ourdefault();
    if ((fd = Uopen(name, (O_BINARY|O_RDWR|O_CREAT), 0666L)) < 0 ||
	     write(fd, (char *) &ourdefault, sizeof(ourdefault))
						       != sizeof(ourdefault) ||
	     write(fd, (char *) &ourentries, sizeof(ourentries))
						       != sizeof(ourentries)) {
	retval = ROMlib_maperrno();
	Uclose(fd);
    } else {
	retval = noErr;
	*fdp = fd;
    }
    fs_err_hook (retval);
    return retval;
}
Exemple #3
0
int Ctype (int TermNum, ARGPTR Args) 
{
  int i, n, fd ;
  char buf[256] ;

  if (Args->num == 0)
    {
      sprintf (buf, "\aThe usage for the type command is: \"type <filenames>\"\n") ;
      Writeterm (buf, strlen(buf), TermNum) ;
      return FALSE ;
    }

  for (i = 1 ; i <= Args->num ; i++)
    {
      if (!strcmp (Args->arg[i], "accounts") && TermNum)
	{
	  sprintf (buf, "\aThe account file can only be accessed from Terminal 0.\n") ;
	  Writeterm (buf, strlen(buf), TermNum) ;
	  continue ;
	}
      if (-1 == (fd = Uopen (Args->arg[i], 1)))
	{
	  sprintf (buf, "\a\"%s\" does not exist.\n", Args->arg[i]) ;
	  Writeterm (buf, strlen(buf), TermNum) ;
	  continue ;
	}
      sprintf (buf, "%s:\n", Args->arg[i]) ;
      Writeterm (buf, strlen(buf), TermNum) ;
      while (n = Uread (fd, buf, 255)) 
	{
	  buf[n] = '\0' ;
	  Writeterm (buf, strlen(buf), TermNum) ;
	}
      Uclose (fd) ;
      Writeterm ("\n", 1, TermNum) ;
    }
  return TRUE ;
} /* Ctype */
Exemple #4
0
BOOL
autoreply_transport_entry(
  transport_instance *tblock,      /* data for this instantiation */
  address_item *addr)              /* address we are working on */
{
int fd, pid, rc;
int cache_fd = -1;
int log_fd = -1;
int cache_size = 0;
int add_size = 0;
EXIM_DB *dbm_file = NULL;
BOOL file_expand, return_message;
uschar *from, *reply_to, *to, *cc, *bcc, *subject, *headers, *text, *file;
uschar *logfile, *oncelog;
uschar *cache_buff = NULL;
uschar *cache_time = NULL;
uschar *message_id = NULL;
header_line *h;
time_t now = time(NULL);
time_t once_repeat_sec = 0;
FILE *f;
FILE *ff = NULL;

autoreply_transport_options_block *ob =
  (autoreply_transport_options_block *)(tblock->options_block);

DEBUG(D_transport) debug_printf("%s transport entered\n", tblock->name);

/* Set up for the good case */

addr->transport_return = OK;
addr->basic_errno = 0;

/* If the address is pointing to a reply block, then take all the data
from that block. It has typically been set up by a mail filter processing
router. Otherwise, the data must be supplied by this transport, and
it has to be expanded here. */

if (addr->reply != NULL)
  {
  DEBUG(D_transport) debug_printf("taking data from address\n");
  from = addr->reply->from;
  reply_to = addr->reply->reply_to;
  to = addr->reply->to;
  cc = addr->reply->cc;
  bcc = addr->reply->bcc;
  subject = addr->reply->subject;
  headers = addr->reply->headers;
  text = addr->reply->text;
  file = addr->reply->file;
  logfile = addr->reply->logfile;
  oncelog = addr->reply->oncelog;
  once_repeat_sec = addr->reply->once_repeat;
  file_expand = addr->reply->file_expand;
  expand_forbid = addr->reply->expand_forbid;
  return_message = addr->reply->return_message;
  }
else
  {
  uschar *oncerepeat = ob->once_repeat;

  DEBUG(D_transport) debug_printf("taking data from transport\n");
  from = ob->from;
  reply_to = ob->reply_to;
  to = ob->to;
  cc = ob->cc;
  bcc = ob->bcc;
  subject = ob->subject;
  headers = ob->headers;
  text = ob->text;
  file = ob->file;
  logfile = ob->logfile;
  oncelog = ob->oncelog;
  file_expand = ob->file_expand;
  return_message = ob->return_message;

  if ((from  != NULL &&
        (from = checkexpand(from, addr, tblock->name, cke_hdr)) == NULL) ||
      (reply_to    != NULL &&
        (reply_to = checkexpand(reply_to, addr, tblock->name, cke_hdr)) == NULL) ||
      (to    != NULL &&
        (to = checkexpand(to, addr, tblock->name, cke_hdr)) == NULL) ||
      (cc    != NULL &&
        (cc = checkexpand(cc, addr, tblock->name, cke_hdr)) == NULL) ||
      (bcc   != NULL &&
        (bcc = checkexpand(bcc, addr, tblock->name, cke_hdr)) == NULL) ||
      (subject   != NULL &&
        (subject = checkexpand(subject, addr, tblock->name, cke_hdr)) == NULL) ||
      (headers != NULL &&
        (headers = checkexpand(headers, addr, tblock->name, cke_text)) == NULL) ||
      (text  != NULL &&
        (text = checkexpand(text, addr, tblock->name, cke_text)) == NULL) ||
      (file  != NULL &&
        (file = checkexpand(file, addr, tblock->name, cke_file)) == NULL) ||
      (logfile != NULL &&
        (logfile = checkexpand(logfile, addr, tblock->name, cke_file)) == NULL) ||
      (oncelog != NULL &&
        (oncelog = checkexpand(oncelog, addr, tblock->name, cke_file)) == NULL) ||
      (oncerepeat != NULL &&
        (oncerepeat = checkexpand(oncerepeat, addr, tblock->name, cke_file)) == NULL))
    return FALSE;

  if (oncerepeat != NULL)
    {
    once_repeat_sec = readconf_readtime(oncerepeat, 0, FALSE);
    if (once_repeat_sec < 0)
      {
      addr->transport_return = FAIL;
      addr->message = string_sprintf("Invalid time value \"%s\" for "
        "\"once_repeat\" in %s transport", oncerepeat, tblock->name);
      return FALSE;
      }
    }
  }

/* If the never_mail option is set, we have to scan all the recipients and
remove those that match. */

if (ob->never_mail != NULL)
  {
  uschar *never_mail = expand_string(ob->never_mail);

  if (never_mail == NULL)
    {
    addr->transport_return = FAIL;
    addr->message = string_sprintf("Failed to expand \"%s\" for "
      "\"never_mail\" in %s transport", ob->never_mail, tblock->name);
    return FALSE;
    }

  if (to != NULL) check_never_mail(&to, never_mail);
  if (cc != NULL) check_never_mail(&cc, never_mail);
  if (bcc != NULL) check_never_mail(&bcc, never_mail);

  if (to == NULL && cc == NULL && bcc == NULL)
    {
    DEBUG(D_transport)
      debug_printf("*** all recipients removed by never_mail\n");
    return OK;
    }
  }

/* If the -N option is set, can't do any more. */

if (dont_deliver)
  {
  DEBUG(D_transport)
    debug_printf("*** delivery by %s transport bypassed by -N option\n",
      tblock->name);
  return FALSE;
  }


/* If the oncelog field is set, we send want to send only one message to the
given recipient(s). This works only on the "To" field. If there is no "To"
field, the message is always sent. If the To: field contains more than one
recipient, the effect might not be quite as envisaged. If once_file_size is
set, instead of a dbm file, we use a regular file containing a circular buffer
recipient cache. */

if (oncelog != NULL && *oncelog != 0 && to != NULL)
  {
  time_t then = 0;

  /* Handle fixed-size cache file. */

  if (ob->once_file_size > 0)
    {
    uschar *p;
    struct stat statbuf;
    cache_fd = Uopen(oncelog, O_CREAT|O_RDWR, ob->mode);

    if (cache_fd < 0 || fstat(cache_fd, &statbuf) != 0)
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("Failed to %s \"once\" file %s when "
        "sending message from %s transport: %s",
        (cache_fd < 0)? "open" : "stat", oncelog, tblock->name,
          strerror(errno));
      goto END_OFF;
      }

    /* Get store in the temporary pool and read the entire file into it. We get
    an amount of store that is big enough to add the new entry on the end if we
    need to do that. */

    cache_size = statbuf.st_size;
    add_size = sizeof(time_t) + Ustrlen(to) + 1;
    cache_buff = store_get(cache_size + add_size);

    if (read(cache_fd, cache_buff, cache_size) != cache_size)
      {
      addr->transport_return = DEFER;
      addr->basic_errno = errno;
      addr->message = US"error while reading \"once\" file";
      goto END_OFF;
      }

    DEBUG(D_transport) debug_printf("%d bytes read from %s\n", cache_size, oncelog);

    /* Scan the data for this recipient. Each entry in the file starts with
    a time_t sized time value, followed by the address, followed by a binary
    zero. If we find a match, put the time into "then", and the place where it
    was found into "cache_time". Otherwise, "then" is left at zero. */

    p = cache_buff;
    while (p < cache_buff + cache_size)
      {
      uschar *s = p + sizeof(time_t);
      uschar *nextp = s + Ustrlen(s) + 1;
      if (Ustrcmp(to, s) == 0)
        {
        memcpy(&then, p, sizeof(time_t));
        cache_time = p;
        break;
        }
      p = nextp;
      }
    }

  /* Use a DBM file for the list of previous recipients. */

  else
    {
    EXIM_DATUM key_datum, result_datum;
    EXIM_DBOPEN(oncelog, O_RDWR|O_CREAT, ob->mode, &dbm_file);
    if (dbm_file == NULL)
      {
      addr->transport_return = DEFER;
      addr->message = string_sprintf("Failed to open %s file %s when sending "
        "message from %s transport: %s", EXIM_DBTYPE, oncelog, tblock->name,
        strerror(errno));
      goto END_OFF;
      }

    EXIM_DATUM_INIT(key_datum);        /* Some DBM libraries need datums */
    EXIM_DATUM_INIT(result_datum);     /* to be cleared */
    EXIM_DATUM_DATA(key_datum) = CS to;
    EXIM_DATUM_SIZE(key_datum) = Ustrlen(to) + 1;

    if (EXIM_DBGET(dbm_file, key_datum, result_datum))
      {
      /* If the datum size is that of a binary time, we are in the new world
      where messages are sent periodically. Otherwise the file is an old one,
      where the datum was filled with a tod_log time, which is assumed to be
      different in size. For that, only one message is ever sent. This change
      introduced at Exim 3.00. In a couple of years' time the test on the size
      can be abolished. */

      if (EXIM_DATUM_SIZE(result_datum) == sizeof(time_t))
        {
        memcpy(&then, EXIM_DATUM_DATA(result_datum), sizeof(time_t));
        }
      else then = now;
      }
    }

  /* Either "then" is set zero, if no message has yet been sent, or it
  is set to the time of the last sending. */

  if (then != 0 && (once_repeat_sec <= 0 || now - then < once_repeat_sec))
    {
    DEBUG(D_transport) debug_printf("message previously sent to %s%s\n", to,
      (once_repeat_sec > 0)? " and repeat time not reached" : "");
    log_fd = Uopen(logfile, O_WRONLY|O_APPEND|O_CREAT, ob->mode);
    if (log_fd >= 0)
      {
      uschar *ptr = log_buffer;
      sprintf(CS ptr, "%s\n  previously sent to %.200s\n", tod_stamp(tod_log), to);
      while(*ptr) ptr++;
      if(write(log_fd, log_buffer, ptr - log_buffer) != ptr-log_buffer
        || close(log_fd))
        DEBUG(D_transport) debug_printf("Problem writing log file %s for %s "
          "transport\n", logfile, tblock->name);
      }
    goto END_OFF;
    }

  DEBUG(D_transport) debug_printf("%s %s\n", (then <= 0)?
    "no previous message sent to" : "repeat time reached for", to);
  }

/* We are going to send a message. Ensure any requested file is available. */

if (file != NULL)
  {
  ff = Ufopen(file, "rb");
  if (ff == NULL && !ob->file_optional)
    {
    addr->transport_return = DEFER;
    addr->message = string_sprintf("Failed to open file %s when sending "
      "message from %s transport: %s", file, tblock->name, strerror(errno));
    return FALSE;
    }
  }

/* Make a subprocess to send the message */

pid = child_open_exim(&fd);

/* Creation of child failed; defer this delivery. */

if (pid < 0)
  {
  addr->transport_return = DEFER;
  addr->message = string_sprintf("Failed to create child process to send "
    "message from %s transport: %s", tblock->name, strerror(errno));
  DEBUG(D_transport) debug_printf("%s\n", addr->message);
  return FALSE;
  }

/* Create the message to be sent - recipients are taken from the headers,
as the -t option is used. The "headers" stuff *must* be last in case there
are newlines in it which might, if placed earlier, screw up other headers. */

f = fdopen(fd, "wb");

if (from != NULL) fprintf(f, "From: %s\n", from);
if (reply_to != NULL) fprintf(f, "Reply-To: %s\n", reply_to);
if (to != NULL) fprintf(f, "To: %s\n", to);
if (cc != NULL) fprintf(f, "Cc: %s\n", cc);
if (bcc != NULL) fprintf(f, "Bcc: %s\n", bcc);
if (subject != NULL) fprintf(f, "Subject: %s\n", subject);

/* Generate In-Reply-To from the message_id header; there should
always be one, but code defensively. */

for (h = header_list; h != NULL; h = h->next)
  if (h->type == htype_id) break;

if (h != NULL)
  {
  message_id = Ustrchr(h->text, ':') + 1;
  while (isspace(*message_id)) message_id++;
  fprintf(f, "In-Reply-To: %s", message_id);
  }

/* Generate a References header if there is at least one of Message-ID:,
References:, or In-Reply-To: (see RFC 2822). */

for (h = header_list; h != NULL; h = h->next)
  if (h->type != htype_old && strncmpic(US"References:", h->text, 11) == 0)
    break;

if (h == NULL)
  for (h = header_list; h != NULL; h = h->next)
    if (h->type != htype_old && strncmpic(US"In-Reply-To:", h->text, 12) == 0)
      break;

/* We limit the total length of references.  Although there is no fixed
limit, some systems do not like headers growing beyond recognition.
Keep the first message ID for the thread root and the last few for
the position inside the thread, up to a maximum of 12 altogether. */

if (h != NULL || message_id != NULL)
  {
  fprintf(f, "References:");
  if (h != NULL)
    {
    uschar *s, *id, *error;
    uschar *referenced_ids[12];
    int reference_count = 0;
    int i;

    s = Ustrchr(h->text, ':') + 1;
    parse_allow_group = FALSE;
    while (*s != 0 && (s = parse_message_id(s, &id, &error)) != NULL)
      {
      if (reference_count == sizeof(referenced_ids)/sizeof(uschar *))
        {
        memmove(referenced_ids + 1, referenced_ids + 2,
           sizeof(referenced_ids) - 2*sizeof(uschar *));
        referenced_ids[reference_count - 1] = id;
        }
      else referenced_ids[reference_count++] = id;
      }
    for (i = 0; i < reference_count; ++i) fprintf(f, " %s", referenced_ids[i]);
    }

  /* The message id will have a newline on the end of it. */

  if (message_id != NULL) fprintf(f, " %s", message_id);
    else fprintf(f, "\n");
  }

/* Add an Auto-Submitted: header */

fprintf(f, "Auto-Submitted: auto-replied\n");

/* Add any specially requested headers */

if (headers != NULL) fprintf(f, "%s\n", headers);
fprintf(f, "\n");

if (text != NULL)
  {
  fprintf(f, "%s", CS text);
  if (text[Ustrlen(text)-1] != '\n') fprintf(f, "\n");
  }

if (ff != NULL)
  {
  while (Ufgets(big_buffer, big_buffer_size, ff) != NULL)
    {
    if (file_expand)
      {
      uschar *s = expand_string(big_buffer);
      DEBUG(D_transport)
        {
        if (s == NULL)
          debug_printf("error while expanding line from file:\n  %s\n  %s\n",
            big_buffer, expand_string_message);
        }
      fprintf(f, "%s", (s == NULL)? CS big_buffer : CS s);
      }
    else fprintf(f, "%s", CS big_buffer);
    }
  }
Exemple #5
0
BOOL
spool_open_datafile(uschar *id)
{
int i;
struct stat statbuf;
flock_t lock_data;
uschar spoolname[256];

/* If split_spool_directory is set, first look for the file in the appropriate
sub-directory of the input directory. If it is not found there, try the input
directory itself, to pick up leftovers from before the splitting. If split_
spool_directory is not set, first look in the main input directory. If it is
not found there, try the split sub-directory, in case it is left over from a
splitting state. */

for (i = 0; i < 2; i++)
  {
  int save_errno;
  message_subdir[0] = (split_spool_directory == (i == 0))? id[5] : 0;
  sprintf(CS spoolname, "%s/input/%s/%s-D", spool_directory, message_subdir, id);
  deliver_datafile = Uopen(spoolname, O_RDWR | O_APPEND, 0);
  if (deliver_datafile >= 0) break;
  save_errno = errno;
  if (errno == ENOENT)
    {
    if (i == 0) continue;
    if (!queue_running)
      log_write(0, LOG_MAIN, "Spool file %s-D not found", id);
    }
  else log_write(0, LOG_MAIN, "Spool error for %s: %s", spoolname,
    strerror(errno));
  errno = save_errno;
  return FALSE;
  }

/* File is open and message_subdir is set. Set the close-on-exec flag, and lock
the file. We lock only the first line of the file (containing the message ID)
because this apparently is needed for running Exim under Cygwin. If the entire
file is locked in one process, a sub-process cannot access it, even when passed
an open file descriptor (at least, I think that's the Cygwin story). On real
Unix systems it doesn't make any difference as long as Exim is consistent in
what it locks. */

(void)fcntl(deliver_datafile, F_SETFD, fcntl(deliver_datafile, F_GETFD) |
  FD_CLOEXEC);

lock_data.l_type = F_WRLCK;
lock_data.l_whence = SEEK_SET;
lock_data.l_start = 0;
lock_data.l_len = SPOOL_DATA_START_OFFSET;

if (fcntl(deliver_datafile, F_SETLK, &lock_data) < 0)
  {
  log_write(L_skip_delivery,
            LOG_MAIN,
            "Spool file is locked (another process is handling this message)");
  (void)close(deliver_datafile);
  deliver_datafile = -1;
  errno = 0;
  return FALSE;
  }

/* Get the size of the data; don't include the leading filename line
in the count, but add one for the newline before the data. */

if (fstat(deliver_datafile, &statbuf) == 0)
  {
  message_body_size = statbuf.st_size - SPOOL_DATA_START_OFFSET;
  message_size = message_body_size + 1;
  }

return TRUE;
}
Exemple #6
0
A2(PUBLIC, OSErr, ROMlib_serialopen, ParmBlkPtr, pbp,		/* INTERNAL */
								 DCtlPtr, dcp)
{
    OSErr err;
    auto DCtlPtr otherp;	/* auto due to old compiler bug */
    hiddenh h;
#if defined (LINUX) || defined (NEXTSTEP)
    const char *devname, *tempname;
    LONGINT fd, ourpid, theirpid, newfd, oumask;
#endif

    err = noErr;
    if (!(dcp->dCtlFlags & CWC(OPENBIT))) {
	h = (hiddenh) NewHandle(sizeof(hidden));
	dcp->dCtlStorage = (Handle) RM(h);
	otherp = otherdctl(pbp);
	if (otherp && (otherp->dCtlFlags & CWC(OPENBIT))) {
	    *STARH(h) = *STARH((hiddenh) (long) MR(otherp->dCtlStorage));
	    dcp->dCtlFlags |= CWC(OPENBIT);
	} else {
#if defined (LINUX) || defined (NEXTSTEP)
	    err = permErr;
	    if ((devname = specialname(pbp, &lockname, &tempname))) {
		oumask = umask(0);
		if (!tempname)
		  err = noErr;
		else if ((fd = Uopen(tempname, O_BINARY|O_CREAT|O_WRONLY, 0666L))
									 >= 0) {
		    ourpid = getpid();
		    if (write(fd, &ourpid, sizeof(ourpid)) == sizeof(ourpid)) {
			if (Ulink(tempname, lockname) == 0)
			    err = noErr;
			else {
			    if ((newfd = Uopen(lockname, O_BINARY|O_RDWR, 0))
									>= 0) {
				if (read(newfd, &theirpid, sizeof(theirpid))
							   == sizeof(theirpid))
				    if ((kill(theirpid, 0) != 0) &&
							      errno == ESRCH) {
					err = noErr;
					Uunlink(lockname);
					Ulink(tempname, lockname);
				    }
				close(newfd);
			    }
			}
			Uunlink(tempname);
		    }
		    close(fd);
		}
		umask(oumask);
	    }
#endif
	    if (err == noErr) {
#if defined (LINUX) || defined (NEXTSTEP)
		HxX(h, fd) = ROMlib_priv_open(devname, O_BINARY|O_RDWR);
		if (HxX(h, fd) < 0) 
		    err = HxX(h, fd);	/* error return piggybacked */
		else {
#if defined(TERMIO)
		    err = ioctl(HxX(h, fd), TCGETA, &HxX(h, state)) < 0 ?
						     ROMlib_maperrno() : noErr;
#else
		    if (ioctl(HxX(h, fd), TIOCGETP, &HxX(h, sgttyb)) < 0 ||
			    ioctl(HxX(h, fd), TIOCGETC, &HxX(h, tchars)) < 0 ||
			     ioctl(HxX(h, fd), TIOCLGET, &HxX(h, lclmode)) < 0)
			err = ROMlib_maperrno();
#endif
#else
		    HxX(h, fd) = (CW(pbp->cntrlParam.ioCRefNum) == AINREFNUM ||
		      CW(pbp->cntrlParam.ioCRefNum) == AOUTREFNUM) ? 0 : 1;
#endif
		    dcp->dCtlFlags |= CWC(OPENBIT);
		    SerReset(CW(pbp->cntrlParam.ioCRefNum),
			    (CW(pbp->cntrlParam.ioCRefNum) == AINREFNUM ||
			     CW(pbp->cntrlParam.ioCRefNum) == AOUTREFNUM) ?
						    CW(SPPortA) : CW(SPPortB));
#if defined (LINUX) || defined (NEXTSTEP)
		}
#endif
	    }
	}
    }
#if defined(SERIALDEBUG)
    warning_trace_info("serial open returning %d", (LONGINT) err);
#endif
    DOCOMPLETION(pbp, err);
}
Exemple #7
0
open_db *
dbfn_open(uschar *name, int flags, open_db *dbblock, BOOL lof)
{
int rc, save_errno;
BOOL read_only = flags == O_RDONLY;
BOOL created = FALSE;
flock_t lock_data;
uschar buffer[256];

/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
open it. Early versions tried to lock on the open database itself, but that
gave rise to mysterious problems from time to time - it was suspected that some
DB libraries "do things" on their open() calls which break the interlocking.
The lock file is never written to, but we open it for writing so we can get a
write lock if required. If it does not exist, we create it. This is done
separately so we know when we have done it, because when running as root we
need to change the ownership - see the bottom of this function. We also try to
make the directory as well, just in case. We won't be doing this many times
unnecessarily, because usually the lock file will be there. If the directory
exists, there is no error. */

sprintf(CS buffer, "%s/db/%s.lockfile", spool_directory, name);

if ((dbblock->lockfd = Uopen(buffer, O_RDWR, EXIMDB_LOCKFILE_MODE)) < 0)
  {
  created = TRUE;
  (void)directory_make(spool_directory, US"db", EXIMDB_DIRECTORY_MODE, TRUE);
  dbblock->lockfd = Uopen(buffer, O_RDWR|O_CREAT, EXIMDB_LOCKFILE_MODE);
  }

if (dbblock->lockfd < 0)
  {
  log_write(0, LOG_MAIN, "%s",
    string_open_failed(errno, "database lock file %s", buffer));
  errno = 0;      /* Indicates locking failure */
  return NULL;
  }

/* Now we must get a lock on the opened lock file; do this with a blocking
lock that times out. */

lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;

DEBUG(D_hints_lookup|D_retry|D_route|D_deliver)
  debug_printf("locking %s\n", buffer);

sigalrm_seen = FALSE;
alarm(EXIMDB_LOCK_TIMEOUT);
rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
alarm(0);

if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
  {
  log_write(0, LOG_MAIN|LOG_PANIC, "Failed to get %s lock for %s: %s",
    read_only? "read" : "write", buffer,
    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
  (void)close(dbblock->lockfd);
  errno = 0;       /* Indicates locking failure */
  return NULL;
  }

DEBUG(D_hints_lookup) debug_printf("locked %s\n", buffer);

/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. If we are
expected to create it, don't do so at first, again so that we can detect
whether we need to change its ownership (see comments about the lock file
above.) There have been regular reports of crashes while opening hints
databases - often this is caused by non-matching db.h and the library. To make
it easy to pin this down, there are now debug statements on either side of the
open call. */

sprintf(CS buffer, "%s/db/%s", spool_directory, name);
DEBUG(D_hints_lookup) debug_printf("EXIM_DBOPEN(%s)\n", buffer);
EXIM_DBOPEN(buffer, flags, EXIMDB_MODE, &(dbblock->dbptr));
DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");

if (dbblock->dbptr == NULL && errno == ENOENT && flags == O_RDWR)
  {
  DEBUG(D_hints_lookup)
    debug_printf("%s appears not to exist: trying to create\n", buffer);
  created = TRUE;
  EXIM_DBOPEN(buffer, flags|O_CREAT, EXIMDB_MODE, &(dbblock->dbptr));
  DEBUG(D_hints_lookup) debug_printf("returned from EXIM_DBOPEN\n");
  }

save_errno = errno;

/* If we are running as root and this is the first access to the database, its
files will be owned by root. We want them to be owned by exim. We detect this
situation by noting above when we had to create the lock file or the database
itself. Because the different dbm libraries use different extensions for their
files, I don't know of any easier way of arranging this than scanning the
directory for files with the appropriate base name. At least this deals with
the lock file at the same time. Also, the directory will typically have only
half a dozen files, so the scan will be quick.

This code is placed here, before the test for successful opening, because there
was a case when a file was created, but the DBM library still returned NULL
because of some problem. It also sorts out the lock file if that was created
but creation of the database file failed. */

if (created && geteuid() == root_uid)
  {
  DIR *dd;
  struct dirent *ent;
  uschar *lastname = Ustrrchr(buffer, '/') + 1;
  int namelen = Ustrlen(name);

  *lastname = 0;
  dd = opendir(CS buffer);

  while ((ent = readdir(dd)) != NULL)
    {
    if (Ustrncmp(ent->d_name, name, namelen) == 0)
      {
      struct stat statbuf;
      Ustrcpy(lastname, ent->d_name);
      if (Ustat(buffer, &statbuf) >= 0 && statbuf.st_uid != exim_uid)
        {
        DEBUG(D_hints_lookup) debug_printf("ensuring %s is owned by exim\n", buffer);
        (void)Uchown(buffer, exim_uid, exim_gid);
        }
      }
    }

  closedir(dd);
  }

/* If the open has failed, return NULL, leaving errno set. If lof is TRUE,
log the event - also for debugging - but not if the file just doesn't exist. */

if (dbblock->dbptr == NULL)
  {
  if (save_errno != ENOENT)
    {
    if (lof)
      log_write(0, LOG_MAIN, "%s", string_open_failed(save_errno, "DB file %s",
        buffer));
    else
      DEBUG(D_hints_lookup)
        debug_printf("%s", CS string_open_failed(save_errno, "DB file %s\n",
          buffer));
    }
  (void)close(dbblock->lockfd);
  errno = save_errno;
  return NULL;
  }

DEBUG(D_hints_lookup)
  debug_printf("opened hints database %s: flags=%s\n", buffer,
    (flags == O_RDONLY)? "O_RDONLY" : (flags == O_RDWR)? "O_RDWR" :
    (flags == (O_RDWR|O_CREAT))? "O_RDWR|O_CREAT" : "??");

/* Pass back the block containing the opened database handle and the open fd
for the lock. */

return dbblock;
}
Exemple #8
0
static open_db *
dbfn_open(uschar *spool, uschar *name, int flags, open_db *dbblock)
{
int rc;
struct flock lock_data;
BOOL read_only = flags == O_RDONLY;
uschar buffer[256];

/* The first thing to do is to open a separate file on which to lock. This
ensures that Exim has exclusive use of the database before it even tries to
open it. If there is a database, there should be a lock file in existence. */

sprintf(CS buffer, "%s/db/%s.lockfile", spool, name);

dbblock->lockfd = Uopen(buffer, flags, 0);
if (dbblock->lockfd < 0)
  {
  printf("** Failed to open database lock file %s: %s\n", buffer,
    strerror(errno));
  return NULL;
  }

/* Now we must get a lock on the opened lock file; do this with a blocking
lock that times out. */

lock_data.l_type = read_only? F_RDLCK : F_WRLCK;
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;

sigalrm_seen = FALSE;
os_non_restarting_signal(SIGALRM, sigalrm_handler);
alarm(EXIMDB_LOCK_TIMEOUT);
rc = fcntl(dbblock->lockfd, F_SETLKW, &lock_data);
alarm(0);

if (sigalrm_seen) errno = ETIMEDOUT;
if (rc < 0)
  {
  printf("** Failed to get %s lock for %s: %s",
    ((flags & O_RDONLY) != 0)? "read" : "write", buffer,
    (errno == ETIMEDOUT)? "timed out" : strerror(errno));
  (void)close(dbblock->lockfd);
  return NULL;
  }

/* At this point we have an opened and locked separate lock file, that is,
exclusive access to the database, so we can go ahead and open it. */

sprintf(CS buffer, "%s/db/%s", spool, name);
EXIM_DBOPEN(buffer, flags, 0, &(dbblock->dbptr));

if (dbblock->dbptr == NULL)
  {
  printf("** Failed to open DBM file %s for %s:\n   %s%s\n", buffer,
    read_only? "reading" : "writing", strerror(errno),
    #ifdef USE_DB
    " (or Berkeley DB error while opening)"
    #else
    ""
    #endif
    );
  (void)close(dbblock->lockfd);
  return NULL;
  }

return dbblock;
}
Exemple #9
0
static int
init_dh(host_item *host)
{
int fd;
int ret;
gnutls_datum m;
uschar filename[200];

/* Initialize the data structures for holding the parameters */

ret = gnutls_dh_params_init(&dh_params);
if (ret < 0) return tls_error(US"init dh_params", host, gnutls_strerror(ret));

/* Set up the name of the cache file */

if (!string_format(filename, sizeof(filename), "%s/gnutls-params",
      spool_directory))
  return tls_error(US"overlong filename", host, NULL);

/* Open the cache file for reading and if successful, read it and set up the
parameters. */

fd = Uopen(filename, O_RDONLY, 0);
if (fd >= 0)
  {
  struct stat statbuf;
  if (fstat(fd, &statbuf) < 0)
    {
    (void)close(fd);
    return tls_error(US"TLS cache stat failed", host, strerror(errno));
    }

  m.size = statbuf.st_size;
  m.data = malloc(m.size);
  if (m.data == NULL)
    return tls_error(US"memory allocation failed", host, strerror(errno));
  errno = 0;
  if (read(fd, m.data, m.size) != m.size)
    return tls_error(US"TLS cache read failed", host, strerror(errno));
  (void)close(fd);

  ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
  if (ret < 0)
    return tls_error(US"DH params import", host, gnutls_strerror(ret));
  DEBUG(D_tls) debug_printf("read D-H parameters from file\n");

  free(m.data);
  }

/* If the file does not exist, fall through to compute new data and cache it.
If there was any other opening error, it is serious. */

else if (errno == ENOENT)
  {
  ret = -1;
  DEBUG(D_tls)
    debug_printf("parameter cache file %s does not exist\n", filename);
  }
else
  return tls_error(string_open_failed(errno, "%s for reading", filename),
    host, NULL);

/* If ret < 0, either the cache file does not exist, or the data it contains
is not useful. One particular case of this is when upgrading from an older
release of Exim in which the data was stored in a different format. We don't
try to be clever and support both formats; we just regenerate new data in this
case. */

if (ret < 0)
  {
  uschar tempfilename[sizeof(filename) + 10];

  DEBUG(D_tls) debug_printf("generating %d bit Diffie-Hellman key...\n",
    DH_BITS);
  ret = gnutls_dh_params_generate2(dh_params, DH_BITS);
  if (ret < 0) return tls_error(US"D-H key generation", host, gnutls_strerror(ret));

  /* Write the parameters to a file in the spool directory so that we
  can use them from other Exim processes. */

  sprintf(CS tempfilename, "%s-%d", filename, (int)getpid());
  fd = Uopen(tempfilename, O_WRONLY|O_CREAT, 0400);
  if (fd < 0)
    return tls_error(string_open_failed(errno, "%s for writing", filename),
      host, NULL);
  (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */

  /* export the parameters in a format that can be generated using GNUTLS'
   * certtool or other programs.
   *
   * The commands for certtool are:
   * $ certtool --generate-dh-params --bits 1024 > params
   */

  m.size = PARAM_SIZE;
  m.data = malloc(m.size);
  if (m.data == NULL)
    return tls_error(US"memory allocation failed", host, strerror(errno));

  m.size = PARAM_SIZE;
  ret = gnutls_dh_params_export_pkcs3(dh_params, GNUTLS_X509_FMT_PEM, m.data,
    &m.size);
  if (ret < 0)
    return tls_error(US"DH params export", host, gnutls_strerror(ret));

  m.size = Ustrlen(m.data);
  errno = 0;
  if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1)
    return tls_error(US"TLS cache write failed", host, strerror(errno));

  free(m.data);
  (void)close(fd);

  if (rename(CS tempfilename, CS filename) < 0)
    return tls_error(string_sprintf("failed to rename %s as %s",
      tempfilename, filename), host, strerror(errno));

  DEBUG(D_tls) debug_printf("wrote D-H parameters to file %s\n", filename);
  }

DEBUG(D_tls) debug_printf("initialized D-H parameters\n");
return OK;
}
int
spool_write_header(uschar *id, int where, uschar **errmsg)
{
int fd;
int i;
int size_correction;
FILE *f;
header_line *h;
struct stat statbuf;
uschar name[256];
uschar temp_name[256];

sprintf(CS temp_name, "%s/input/%s/hdr.%d", spool_directory, message_subdir,
  (int)getpid());
fd = spool_open_temp(temp_name);
if (fd < 0) return spool_write_error(where, errmsg, US"open", NULL, NULL);
f = fdopen(fd, "wb");
DEBUG(D_receive|D_deliver) 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 %ld %ld\n", originator_login, (long int)originator_uid,
  (long 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. The HELO
data can be set for local SMTP as well as remote. */

if (sender_helo_name != NULL)
  fprintf(f, "-helo_name %s\n", sender_helo_name);

if (sender_host_address != NULL)
  {
  fprintf(f, "-host_address %s.%d\n", sender_host_address, sender_host_port);
  if (sender_host_name != NULL)
    fprintf(f, "-host_name %s\n", sender_host_name);
  if (sender_host_authenticated != NULL)
    fprintf(f, "-host_auth %s\n", sender_host_authenticated);
  }

/* Also about the interface a message came in on */

if (interface_address != NULL)
  fprintf(f, "-interface_address %s.%d\n", interface_address, interface_port);

if (smtp_active_hostname != primary_hostname)
  fprintf(f, "-active_hostname %s\n", smtp_active_hostname);

/* 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);

/* Preserve any ACL variables that are set. */

tree_walk(acl_var_c, &acl_var_write, f);
tree_walk(acl_var_m, &acl_var_write, f);

/* Now any other data that needs to be remembered. */

fprintf(f, "-body_linecount %d\n", body_linecount);
fprintf(f, "-max_received_linelength %d\n", max_received_linelength);

if (body_zerocount > 0) fprintf(f, "-body_zerocount %d\n", body_zerocount);

if (authenticated_id != NULL)
  fprintf(f, "-auth_id %s\n", authenticated_id);
if (authenticated_sender != NULL)
  fprintf(f, "-auth_sender %s\n", authenticated_sender);

if (allow_unqualified_recipient) fprintf(f, "-allow_unqualified_recipient\n");
if (allow_unqualified_sender) fprintf(f, "-allow_unqualified_sender\n");
if (deliver_firsttime) fprintf(f, "-deliver_firsttime\n");
if (deliver_freeze) fprintf(f, "-frozen " TIME_T_FMT "\n", deliver_frozen_at);
if (dont_deliver) fprintf(f, "-N\n");
if (host_lookup_deferred) fprintf(f, "-host_lookup_deferred\n");
if (host_lookup_failed) fprintf(f, "-host_lookup_failed\n");
if (sender_local) fprintf(f, "-local\n");
if (local_error_message) fprintf(f, "-localerror\n");
if (local_scan_data != NULL) fprintf(f, "-local_scan %s\n", local_scan_data);
#ifdef WITH_CONTENT_SCAN
if (spam_bar)       fprintf(f,"-spam_bar %s\n",       spam_bar);
if (spam_score)     fprintf(f,"-spam_score %s\n",     spam_score);
if (spam_score_int) fprintf(f,"-spam_score_int %s\n", spam_score_int);
#endif
if (deliver_manual_thaw) fprintf(f, "-manual_thaw\n");
if (sender_set_untrusted) fprintf(f, "-sender_set_untrusted\n");

#ifdef EXPERIMENTAL_BRIGHTMAIL
if (bmi_verdicts != NULL) fprintf(f, "-bmi_verdicts %s\n", bmi_verdicts);
#endif

#ifdef SUPPORT_TLS
if (tls_in.certificate_verified) fprintf(f, "-tls_certificate_verified\n");
if (tls_in.cipher)       fprintf(f, "-tls_cipher %s\n", tls_in.cipher);
if (tls_in.peercert)
  {
  (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert);
  fprintf(f, "-tls_peercert %s\n", CS big_buffer);
  }
if (tls_in.peerdn)       fprintf(f, "-tls_peerdn %s\n", string_printing(tls_in.peerdn));
if (tls_in.sni)		 fprintf(f, "-tls_sni %s\n",    string_printing(tls_in.sni));
if (tls_in.ourcert)
  {
  (void) tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert);
  fprintf(f, "-tls_ourcert %s\n", CS big_buffer);
  }
if (tls_in.ocsp)	 fprintf(f, "-tls_ocsp %d\n",   tls_in.ocsp);
#endif

#ifdef SUPPORT_I18N
if (message_smtputf8)
  {
  fprintf(f, "-smtputf8\n");
  if (message_utf8_downconvert)
    fprintf(f, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : "");
  }
#endif

/* Write the dsn flags to the spool header file */
DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_envid %s\n", dsn_envid);
if (dsn_envid != NULL) fprintf(f, "-dsn_envid %s\n", dsn_envid);
DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret);
if (dsn_ret != 0) fprintf(f, "-dsn_ret %d\n", dsn_ret);

/* 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. If a recipient is a "one-time" alias, it is followed by
a space and its parent address number (pno). */

tree_write(tree_nonrecipients, f);
fprintf(f, "%d\n", recipients_count);
for (i = 0; i < recipients_count; i++)
  {
  recipient_item *r = recipients_list + i;

  DEBUG(D_deliver) debug_printf("DSN: Flags :%d\n", r->dsn_flags);

  if (r->pno < 0 && r->errors_to == NULL && r->dsn_flags == 0)
    fprintf(f, "%s\n", r->address);
  else
    {
    uschar * errors_to = r->errors_to ? r->errors_to : US"";
    /* for DSN SUPPORT extend exim 4 spool in a compatible way by
    adding new values upfront and add flag 0x02 */
    uschar * orcpt = r->orcpt ? r->orcpt : US"";

    fprintf(f, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt),
      r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno);
    }

    DEBUG(D_deliver) debug_printf("DSN: **** SPOOL_OUT - "
      "address: |%s| errorsto: |%s| orcpt: |%s| dsn_flags: %d\n",
      r->address, r->errors_to, r->orcpt, r->dsn_flags);
  }

/* 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);
if (fstat(fd, &statbuf))
  return spool_write_error(where, errmsg, US"fstat", temp_name, f);
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. Don't included them in the message's
size. */

for (h = header_list; h != NULL; h = h->next)
  {
  fprintf(f, "%03d%c %s", h->slen, h->type, h->text);
  size_correction += 5;
  if (h->type == '*') size_correction += h->slen;
  }

/* Flush and check for any errors while writing */

if (fflush(f) != 0 || ferror(f))
  return spool_write_error(where, errmsg, US"write", temp_name, f);

/* Force the file's contents to be written to disk. 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 (EXIMfsync(fileno(f)) < 0)
  return spool_write_error(where, errmsg, US"sync", temp_name, f);

/* Get the size of the file, and close it. */

if (fstat(fd, &statbuf) != 0)
  return spool_write_error(where, errmsg, US"fstat", temp_name, NULL);
if (fclose(f) != 0)
  return spool_write_error(where, errmsg, US"close", temp_name, NULL);

/* Rename the file to its correct name, thereby replacing any previous
incarnation. */

sprintf(CS name, "%s/input/%s/%s-H", spool_directory, message_subdir, id);

if (Urename(temp_name, name) < 0)
  return spool_write_error(where, errmsg, US"rename", temp_name, NULL);

/* Linux (and maybe other OS?) does not automatically sync a directory after
an operation like rename. We therefore have to do it forcibly ourselves in
these cases, to make sure the file is actually accessible on disk, as opposed
to just the data being accessible from a file in lost+found. Linux also has
O_DIRECTORY, for opening a directory.

However, it turns out that some file systems (some versions of NFS?) do not
support directory syncing. It seems safe enough to ignore EINVAL to cope with
these cases. One hack on top of another... but that's life. */

#ifdef NEED_SYNC_DIRECTORY

sprintf(CS temp_name, "%s/input/%s/.", spool_directory, message_subdir);

#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif

if ((fd = Uopen(temp_name, O_RDONLY|O_DIRECTORY, 0)) < 0)
  return spool_write_error(where, errmsg, US"directory open", name, NULL);

if (EXIMfsync(fd) < 0 && errno != EINVAL)
  return spool_write_error(where, errmsg, US"directory sync", name, NULL);

if (close(fd) < 0)
  return spool_write_error(where, errmsg, US"directory close", name, NULL);

#endif  /* NEED_SYNC_DIRECTORY */

/* 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(D_receive) debug_printf("Size of headers = %d\n",
  (int)(statbuf.st_size - size_correction));

return statbuf.st_size - size_correction;
}
Exemple #11
0
A8(PUBLIC, OSErr,  ROMlib_hiddenbyname, GetOrSetType, gors,	/* INTERNAL */
		   char *, pathname, char *, rpathname, Single_dates *, datep,
		   FInfo *, finfop, FXInfo *, fxinfop, LONGINT *, lenp,
							      LONGINT *, rlenp)
{
    LONGINT rfd;
    struct stat sbuf;
    Single_finfo sfinfo;
    Single_descriptor d;
    OSErr retval;
    BOOLEAN done;

    retval = noErr;
    if (Ustat(pathname, &sbuf) < 0)
	retval = ROMlib_maperrno();
    else {
	done = FALSE;
	rfd = Uopen(rpathname, O_BINARY|(gors == Set ? O_RDWR : O_RDONLY), 0);
/*
if (rfd == -1)
fprintf(stderr, "%s(%d): open '%s' fails\n", __FILE__, __LINE__, rpathname);
*/
	if (rfd < 0) {
	    done = TRUE;
	    if (errno == ENOENT) {
		/* no resource fork (or AppleSingle) */
		/* right now we ignore that it could be AppleSingle */
		switch (gors) {
		case Get:
		    memset(datep,   0, sizeof(*datep));
		    memset(finfop,  0, sizeof(*finfop));
		    memset(fxinfop, 0, sizeof(*fxinfop));
		    *lenp = CL(sbuf.st_size);
		    *rlenp = 0;
		    break;
		case Set:
		    retval = ROMlib_newresfork(rpathname, &rfd, FALSE);
		    done = FALSE;
		    break;
		default:
		    gui_assert(0);
		    break;
		}
	    } else
		retval = ROMlib_maperrno();
	}
	if (!done && retval == noErr) {
	    switch (gors) {
	    case Get:
		if (getsetentry(Get, rfd, File_Dates_Info_ID, &d, NULL))
		  getsetpiece(Get, rfd, &d, (char *) datep, sizeof(*datep));
		else
		  {
		    datep->crdat = CL (UNIXTIMETOMACTIME (MIN (sbuf.st_ctime, sbuf.st_mtime)));
		    datep->moddat = CL (UNIXTIMETOMACTIME (sbuf.st_mtime));
		    datep->accessdat = CL (UNIXTIMETOMACTIME (sbuf.st_atime));
		    datep->backupdat = 0;
		  }

		if (!getsetentry(Get, rfd, Finder_Info_ID,   &d, NULL))
		  warning_unexpected ("no finder info");
		else
		  {
		    getsetpiece(Get, rfd, &d, (char *) &sfinfo, sizeof(sfinfo));

		    if (finfop)
		      *finfop  = sfinfo.finfo;
		    if (fxinfop)
		      *fxinfop = sfinfo.fxinfo;

		    *lenp  = CL(sbuf.st_size);
		  }
		if (getsetentry(Get, rfd, Resource_Fork_ID,   &d, NULL))
		    *rlenp = d.length;
		else
		    *rlenp = 0;
		break;
	    case Set:
		if (getsetentry(Get, rfd, File_Dates_Info_ID, &d, NULL))
		  getsetpiece(Set, rfd, &d, (char *) datep, sizeof(*datep));

		if (!getsetentry(Get, rfd, Finder_Info_ID,   &d, NULL))
		  warning_unexpected ("no finfo");
		else
		  {
		    if (!finfop || !fxinfop) {
		      sfinfo.finfo.fdCreator = TICKX("UNIX");
		      sfinfo.finfo.fdType    = TICKX("TEXT");
		      getsetpiece(Get, rfd, &d, (char *) &sfinfo,
							       sizeof(sfinfo));
		    }

		    if (finfop)
		      sfinfo.finfo  = *finfop;
		    if (fxinfop)
		      sfinfo.fxinfo = *fxinfop;
		    getsetpiece(Set, rfd, &d, (char *) &sfinfo, sizeof(sfinfo));
		  }
		break;
	    default:
		gui_assert(0);
		break;
	    }
	}
	Uclose(rfd);
    }
    fs_err_hook (retval);
    return retval;
}