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