void qmgr_active_done(QMGR_MESSAGE *message) { char *myname = "qmgr_active_done"; struct stat st; if (msg_verbose) msg_info("%s: %s", myname, message->queue_id); /* * During a previous iteration, an attempt to bounce this message may * have failed, so there may still be a bounce log lying around. XXX By * groping around in the bounce queue, we're trespassing on the bounce * service's territory. But doing so is more robust than depending on the * bounce daemon to do the lookup for us, and for us to do the deleting * after we have received a successful status from the bounce service. * The bounce queue directory blocks are most likely in memory anyway. If * these lookups become a performance problem we will have to build an * in-core cache into the bounce daemon. * * Don't bounce when the bounce log is empty. The bounce process obviously * failed, and the delivery agent will have requested that the message be * deferred. * * Bounces are sent asynchronously to avoid stalling while the cleanup * daemon waits for the qmgr to accept the "new mail" trigger. */ if (stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_BOUNCE, message->queue_id), &st) == 0) { if (st.st_size == 0) { if (mail_queue_remove(MAIL_QUEUE_BOUNCE, message->queue_id)) msg_fatal("remove %s %s: %m", MAIL_QUEUE_BOUNCE, message->queue_id); } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); if (message->verp_delims == 0 || var_verp_bounce_off) abounce_flush(BOUNCE_FLAG_KEEP, message->queue_name, message->queue_id, message->encoding, message->errors_to, qmgr_active_done_2_bounce_flush, (char *) message); else abounce_flush_verp(BOUNCE_FLAG_KEEP, message->queue_name, message->queue_id, message->encoding, message->errors_to, message->verp_delims, qmgr_active_done_2_bounce_flush, (char *) message); return; } } /* * Asynchronous processing does not reach this point. */ qmgr_active_done_2_generic(message); }
int mail_open_ok(const char *queue_name, const char *queue_id, struct stat * statp, const char **path) { if (mail_queue_name_ok(queue_name) == 0) { msg_warn("bad mail queue name: %s", queue_name); return (MAIL_OPEN_NO); } if (mail_queue_id_ok(queue_id) == 0) return (MAIL_OPEN_NO); /* * I really would like to look up the file attributes *after* opening the * file so that we could save one directory traversal on systems without * name-to-inode cache. However, we don't necessarily always want to open * the file. */ *path = mail_queue_path((VSTRING *) 0, queue_name, queue_id); if (lstat(*path, statp) < 0) { if (errno != ENOENT) msg_warn("%s: %m", *path); return (MAIL_OPEN_NO); } if (!S_ISREG(statp->st_mode)) { msg_warn("%s: uid %ld: not a regular file", *path, (long) statp->st_uid); return (MAIL_OPEN_NO); } if ((statp->st_mode & S_IRWXU) != MAIL_QUEUE_STAT_READY) return (MAIL_OPEN_NO); /* * Workaround for spurious "file has 2 links" warnings in showq. As * kernels have evolved from non-interruptible system calls towards * fine-grained locks, the showq command has become likely to observe a * file while the queue manager is in the middle of renaming it, at a * time when the file has links to both the old and new name. We now log * the warning only when the condition appears to be persistent. */ #define MINUTE_SECONDS 60 /* XXX should be centralized */ if (statp->st_nlink > 1) { if (msg_verbose) msg_info("%s: uid %ld: file has %d links", *path, (long) statp->st_uid, (int) statp->st_nlink); else if (statp->st_ctime < time((time_t *) 0) - MINUTE_SECONDS) msg_warn("%s: uid %ld: file has %d links", *path, (long) statp->st_uid, (int) statp->st_nlink); } return (MAIL_OPEN_YES); }
void qmgr_move(const char *src_queue, const char *dst_queue, time_t time_stamp) { const char *myname = "qmgr_move"; SCAN_DIR *queue_dir; char *queue_id; struct utimbuf tbuf; const char *path; if (strcmp(src_queue, dst_queue) == 0) msg_panic("%s: source queue %s is destination", myname, src_queue); if (msg_verbose) msg_info("start move queue %s -> %s", src_queue, dst_queue); queue_dir = scan_dir_open(src_queue); while ((queue_id = mail_scan_dir_next(queue_dir)) != 0) { if (mail_queue_id_ok(queue_id)) { if (time_stamp > 0) { tbuf.actime = tbuf.modtime = time_stamp; path = mail_queue_path((VSTRING *) 0, src_queue, queue_id); if (utime(path, &tbuf) < 0) { if (errno != ENOENT) msg_fatal("%s: update %s time stamps: %m", myname, path); msg_warn("%s: update %s time stamps: %m", myname, path); continue; } } if (mail_queue_rename(queue_id, src_queue, dst_queue)) { if (errno != ENOENT) msg_fatal("%s: rename %s from %s to %s: %m", myname, queue_id, src_queue, dst_queue); msg_warn("%s: rename %s from %s to %s: %m", myname, queue_id, src_queue, dst_queue); continue; } if (msg_verbose) msg_info("%s: moved %s from %s to %s", myname, queue_id, src_queue, dst_queue); } else { msg_warn("%s: ignored: queue %s id %s", myname, src_queue, queue_id); } } scan_dir_close(queue_dir); if (msg_verbose) msg_info("end move queue %s -> %s", src_queue, dst_queue); }
static void cleanup_trace_append(CLEANUP_STATE *state, RECIPIENT *rcpt, DSN *dsn) { MSG_STATS stats; if (cleanup_trace_path == 0) { cleanup_trace_path = vstring_alloc(10); mail_queue_path(cleanup_trace_path, MAIL_QUEUE_TRACE, state->queue_id); } if (trace_append(BOUNCE_FLAG_CLEAN, state->queue_id, CLEANUP_MSG_STATS(&stats, state), rcpt, "none", dsn) != 0) { msg_warn("%s: trace logfile update error", state->queue_id); state->errs |= CLEANUP_STAT_WRITE; } }
static int flush_refresh_service(int max_age) { const char *myname = "flush_refresh_service"; SCAN_DIR *scan; char *site_path; struct stat st; VSTRING *path = vstring_alloc(10); scan = scan_dir_open(MAIL_QUEUE_FLUSH); while ((site_path = mail_scan_dir_next(scan)) != 0) { if (!mail_queue_id_ok(site_path)) continue; /* XXX grumble. */ mail_queue_path(path, MAIL_QUEUE_FLUSH, site_path); if (stat(STR(path), &st) < 0) { if (errno != ENOENT) msg_warn("%s: stat %s: %m", myname, STR(path)); else if (msg_verbose) msg_info("%s: %s: %m", myname, STR(path)); continue; } if (st.st_size == 0) { if (st.st_mtime + var_fflush_purge < event_time()) { if (unlink(STR(path)) < 0) msg_warn("remove logfile %s: %m", STR(path)); else if (msg_verbose) msg_info("%s: unlink %s, empty and unchanged for %d days", myname, STR(path), var_fflush_purge / 86400); } else if (msg_verbose) msg_info("%s: skip logfile %s - empty log", myname, site_path); } else if (st.st_atime + max_age < event_time()) { if (msg_verbose) msg_info("%s: flush logfile %s", myname, site_path); flush_send_path(site_path, REFRESH_ONLY); } else { if (msg_verbose) msg_info("%s: skip logfile %s, unread for <%d hours(s) ", myname, site_path, max_age / 3600); } } scan_dir_close(scan); vstring_free(path); return (FLUSH_STAT_OK); }
void bounce_cleanup_register(char *service, char *queue_id) { char *myname = "bounce_cleanup_register"; /* * Sanity checks. */ if (bounce_cleanup_path) msg_panic("%s: nested call", myname); /* * Save a copy of the logfile path, and of the last callback function * pointer registered with the run-time error handler. */ bounce_cleanup_path = vstring_alloc(10); (void) mail_queue_path(bounce_cleanup_path, service, queue_id); bounce_cleanup_func = msg_cleanup(bounce_cleanup_callback); signal(SIGTERM, bounce_cleanup_sig); }
static void qmgr_active_defer(const char *queue_name, const char *queue_id, const char *dest_queue, int delay) { const char *myname = "qmgr_active_defer"; const char *path; struct utimbuf tbuf; if (msg_verbose) msg_info("wakeup %s after %ld secs", queue_id, (long) delay); tbuf.actime = tbuf.modtime = event_time() + delay; path = mail_queue_path((VSTRING *) 0, queue_name, queue_id); if (utime(path, &tbuf) < 0 && errno != ENOENT) msg_fatal("%s: update %s time stamps: %m", myname, path); if (mail_queue_rename(queue_id, queue_name, dest_queue)) { if (errno != ENOENT) msg_fatal("%s: rename %s from %s to %s: %m", myname, queue_id, queue_name, dest_queue); msg_warn("%s: rename %s from %s to %s: %m", myname, queue_id, queue_name, dest_queue); } else if (msg_verbose) { msg_info("%s: defer %s", myname, queue_id); } }
static int flush_one_file(const char *queue_id, VSTRING *queue_file, struct utimbuf * tbuf, int how) { const char *myname = "flush_one_file"; const char *queue_name; const char *path; /* * Some other instance of this program may flush some logfile and may * just have moved this queue file to the incoming queue. */ for (queue_name = MAIL_QUEUE_DEFERRED; /* see below */ ; queue_name = MAIL_QUEUE_INCOMING) { path = mail_queue_path(queue_file, queue_name, queue_id); if (utime(path, tbuf) == 0) break; if (errno != ENOENT) msg_warn("%s: update %s time stamps: %m", myname, path); if (STREQ(queue_name, MAIL_QUEUE_INCOMING)) return (0); } /* * With the UNTHROTTLE_AFTER strategy, we leave it up to the queue * manager to unthrottle transports and queues as it reads recipients * from a queue file. We request this unthrottle operation by setting the * group read permission bit. * * Note: we must avoid using chmod(). It is not only slower than fchmod() * but it is also less secure. With chmod(), an attacker could repeatedly * send requests to the flush server and trick it into changing * permissions of non-queue files, by exploiting a race condition. * * We use safe_open() because we don't validate the file content before * modifying the file status. */ if (how & UNTHROTTLE_AFTER) { VSTRING *why; struct stat st; VSTREAM *fp; for (why = vstring_alloc(1); /* see below */ ; queue_name = MAIL_QUEUE_INCOMING, path = mail_queue_path(queue_file, queue_name, queue_id)) { if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) != 0) break; if (errno != ENOENT) msg_warn("%s: open %s: %s", myname, path, STR(why)); if (errno != ENOENT || STREQ(queue_name, MAIL_QUEUE_INCOMING)) { vstring_free(why); return (0); } } vstring_free(why); if ((st.st_mode & MAIL_QUEUE_STAT_READY) != MAIL_QUEUE_STAT_READY) { (void) vstream_fclose(fp); return (0); } if (fchmod(vstream_fileno(fp), st.st_mode | MAIL_QUEUE_STAT_UNTHROTTLE) < 0) msg_warn("%s: fchmod %s: %m", myname, path); (void) vstream_fclose(fp); } /* * Move the file to the incoming queue, if it isn't already there. */ if (STREQ(queue_name, MAIL_QUEUE_INCOMING) == 0 && mail_queue_rename(queue_id, queue_name, MAIL_QUEUE_INCOMING) < 0 && errno != ENOENT) msg_warn("%s: rename from %s to %s: %m", path, queue_name, MAIL_QUEUE_INCOMING); /* * If we got here, we achieved something, so let's claim succes. */ return (1); }