static int flush_send_file_service(const char *queue_id) { const char *myname = "flush_send_file_service"; VSTRING *queue_file; struct utimbuf tbuf; static char qmgr_scan_trigger[] = { QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ }; /* * Sanity check. */ if (!mail_queue_id_ok(queue_id)) return (FLUSH_STAT_BAD); if (msg_verbose) msg_info("%s: requesting delivery for queue_id %s", myname, queue_id); queue_file = vstring_alloc(30); tbuf.actime = tbuf.modtime = event_time(); if (flush_one_file(queue_id, queue_file, &tbuf, UNTHROTTLE_AFTER) > 0) mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); vstring_free(queue_file); return (FLUSH_STAT_OK); }
static int bounce_verp_proto(char *service_name, VSTREAM *client) { char *myname = "bounce_verp_proto"; int flags; /* * Read and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp_delims, ATTR_TYPE_END) != 6) { msg_warn("malformed request"); return (-1); } if (mail_queue_name_ok(STR(queue_name)) == 0) { msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } if (strlen(STR(verp_delims)) != 2) { msg_warn("malformed verp delimiter string: %s", printable(STR(verp_delims), '?')); return (-1); } if (msg_verbose) msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s sender=%s delim=%s", myname, flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(verp_delims)); /* * On request by the client, set up a trap to delete the log file in case * of errors. */ if (flags & BOUNCE_FLAG_CLEAN) bounce_cleanup_register(service_name, STR(queue_id)); /* * Execute the request. Fall back to traditional notification if a bounce * was returned as undeliverable, because we don't want to VERPify those. */ if (!*STR(sender) || !strcasecmp(STR(sender), mail_addr_double_bounce())) { msg_warn("request to send VERP-style notification of bounced mail"); return (bounce_notify_service(flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), STR(sender))); } else return (bounce_notify_verp(flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(verp_delims))); }
static int bounce_notify_proto(char *service_name, VSTREAM *client, int (*service) (int, char *, char *, char *, char *, char *, char *, int, BOUNCE_TEMPLATES *)) { const char *myname = "bounce_notify_proto"; int flags; int dsn_ret; /* * Read and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, &dsn_ret, ATTR_TYPE_END) != 7) { msg_warn("malformed request"); return (-1); } /* * Sanitize input. */ if (mail_queue_name_ok(STR(queue_name)) == 0) { msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } printable(STR(dsn_envid), '?'); if (msg_verbose) msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s sender=%s envid=%s ret=0x%x", myname, flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(dsn_envid), dsn_ret); /* * On request by the client, set up a trap to delete the log file in case * of errors. */ if (flags & BOUNCE_FLAG_CLEAN) bounce_cleanup_register(service_name, STR(queue_id)); /* * Execute the request. */ return (service(flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(dsn_envid), dsn_ret, bounce_templates)); }
static int bounce_one_proto(char *service_name, VSTREAM *client) { char *myname = "bounce_one_proto"; int flags; long offset; /* * Read and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_ORCPT, orig_rcpt, ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, &offset, ATTR_TYPE_STR, MAIL_ATTR_STATUS, dsn_status, ATTR_TYPE_STR, MAIL_ATTR_ACTION, dsn_action, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 11) { msg_warn("malformed request"); return (-1); } if (strcmp(service_name, MAIL_SERVICE_BOUNCE) != 0) { msg_warn("wrong service name \"%s\" for one-recipient bouncing", service_name); return (-1); } if (mail_queue_name_ok(STR(queue_name)) == 0) { msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } if (msg_verbose) msg_info("%s: flags=0x%x queue=%s id=%s encoding=%s sender=%s orig_to=%s to=%s off=%ld stat=%s act=%s why=%s", myname, flags, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(orig_rcpt), STR(recipient), offset, STR(dsn_status), STR(dsn_action), STR(why)); /* * Execute the request. */ return (bounce_one_service(flags, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(orig_rcpt), STR(recipient), offset, STR(dsn_status), STR(dsn_action), STR(why))); }
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 int bounce_append_proto(char *service_name, VSTREAM *client) { char *myname = "bounce_append_proto"; int flags; long offset; /* * Read the and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_STR, MAIL_ATTR_ORCPT, orig_rcpt, ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, &offset, ATTR_TYPE_STR, MAIL_ATTR_STATUS, dsn_status, ATTR_TYPE_STR, MAIL_ATTR_ACTION, dsn_action, ATTR_TYPE_STR, MAIL_ATTR_WHY, why, ATTR_TYPE_END) != 8) { msg_warn("malformed request"); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } if (msg_verbose) msg_info("%s: flags=0x%x service=%s id=%s org_to=%s to=%s off=%ld stat=%s act=%s why=%s", myname, flags, service_name, STR(queue_id), STR(orig_rcpt), STR(recipient), offset, STR(dsn_status), STR(dsn_action), STR(why)); /* * On request by the client, set up a trap to delete the log file in case * of errors. */ if (flags & BOUNCE_FLAG_CLEAN) bounce_cleanup_register(service_name, STR(queue_id)); /* * Execute the request. */ return (bounce_append_service(flags, service_name, STR(queue_id), STR(orig_rcpt), STR(recipient), offset, STR(dsn_status), STR(dsn_action), STR(why))); }
static int flush_add_path(const char *path, const char *queue_id) { const char *myname = "flush_add_path"; VSTREAM *log; /* * Sanity check. */ if (!mail_queue_id_ok(path)) return (FLUSH_STAT_BAD); /* * Open the logfile or bust. */ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_CREAT | O_APPEND | O_WRONLY, 0600)) == 0) msg_fatal("%s: open fast flush logfile %s: %m", myname, path); /* * We must lock the logfile, so that we don't lose information due to * concurrent access. If the lock takes too long, the Postfix watchdog * will eventually take care of the problem, but it will take a while. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); /* * Append the queue ID. With 15 bits of microsecond time, a queue ID is * not recycled often enough for false hits to be a problem. If it does, * then we could add other signature information, such as the file size * in bytes. */ vstream_fprintf(log, "%s\n", queue_id); if (vstream_fflush(log)) msg_warn("write fast flush logfile %s: %m", path); /* * Clean up. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); if (vstream_fclose(log) != 0) msg_warn("write fast flush logfile %s: %m", path); return (FLUSH_STAT_OK); }
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); }
int main(int argc, char **argv) { struct stat st; char *slash; int c; int fd; int mode = PQ_MODE_DEFAULT; char *site_to_flush = 0; char *id_to_flush = 0; ARGV *import_env; int bad_site; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * Be consistent with file permissions. */ umask(022); /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal_status(EX_UNAVAILABLE, "open /dev/null: %m"); /* * Initialize. Set up logging, read the global configuration file and * extract configuration information. Set up signal handlers so that we * can clean up incomplete output. */ if ((slash = strrchr(argv[0], '/')) != 0 && slash[1]) argv[0] = slash + 1; msg_vstream_init(argv[0], VSTREAM_ERR); msg_cleanup(unavailable); msg_syslog_init(mail_task("postqueue"), LOG_PID, LOG_FACILITY); set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* * Check the Postfix library version as soon as we enable logging. */ MAIL_VERSION_CHECK; /* * Parse JCL. This program is set-gid and must sanitize all command-line * parameters. The configuration directory argument is validated by the * mail configuration read routine. Don't do complex things until we have * completed initializations. */ while ((c = GETOPT(argc, argv, "c:fi:ps:v")) > 0) { switch (c) { case 'c': /* non-default configuration */ if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal_status(EX_UNAVAILABLE, "out of memory"); break; case 'f': /* flush queue */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_QUEUE; break; case 'i': /* flush queue file */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_FILE; id_to_flush = optarg; break; case 'p': /* traditional mailq */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_MAILQ_LIST; break; case 's': /* flush site */ if (mode != PQ_MODE_DEFAULT) usage(); mode = PQ_MODE_FLUSH_SITE; site_to_flush = optarg; break; case 'v': if (geteuid() == 0) msg_verbose++; break; default: usage(); } } if (argc > optind) usage(); /* * Further initialization... */ mail_conf_read(); /* Re-evaluate mail_task() after reading main.cf. */ msg_syslog_init(mail_task("postqueue"), LOG_PID, LOG_FACILITY); mail_dict_init(); /* proxy, sql, ldap */ get_mail_conf_str_table(str_table); /* * This program is designed to be set-gid, which makes it a potential * target for attack. If not running as root, strip the environment so we * don't have to trust the C library. If running as root, don't strip the * environment so that showq can receive non-default configuration * directory info when the mail system is down. */ if (geteuid() != 0) { import_env = mail_parm_split(VAR_IMPORT_ENVIRON, var_import_environ); clean_env(import_env->argv); argv_free(import_env); } if (chdir(var_queue_dir)) msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); signal(SIGPIPE, SIG_IGN); /* End of initializations. */ /* * Further input validation. */ if (site_to_flush != 0) { bad_site = 0; if (*site_to_flush == '[') { bad_site = !valid_mailhost_literal(site_to_flush, DONT_GRIPE); } else { bad_site = !valid_hostname(site_to_flush, DONT_GRIPE); } if (bad_site) msg_fatal_status(EX_USAGE, "Cannot flush mail queue - invalid destination: \"%.100s%s\"", site_to_flush, strlen(site_to_flush) > 100 ? "..." : ""); } if (id_to_flush != 0) { if (!mail_queue_id_ok(id_to_flush)) msg_fatal_status(EX_USAGE, "Cannot flush queue ID - invalid name: \"%.100s%s\"", id_to_flush, strlen(id_to_flush) > 100 ? "..." : ""); } /* * Start processing. */ switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case PQ_MODE_MAILQ_LIST: show_queue(); exit(0); break; case PQ_MODE_FLUSH_SITE: flush_site(site_to_flush); exit(0); break; case PQ_MODE_FLUSH_FILE: flush_file(id_to_flush); exit(0); break; case PQ_MODE_FLUSH_QUEUE: flush_queue(); exit(0); break; case PQ_MODE_DEFAULT: usage(); /* NOTREACHED */ } }
static void flush_service(VSTREAM *client_stream, char *unused_service, char **argv) { VSTRING *request = vstring_alloc(10); VSTRING *site = 0; VSTRING *queue_id = 0; static char wakeup[] = { /* master wakeup request */ TRIGGER_REQ_WAKEUP, 0, }; int status = FLUSH_STAT_BAD; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * This routine runs whenever a client connects to the UNIX-domain socket * dedicated to the fast flush service. What we see below is a little * protocol to (1) read a request from the client (the name of the site) * and (2) acknowledge that we have received the request. * * All connection-management stuff is handled by the common code in * single_server.c. */ if (flush_request_receive(client_stream, request) == 0) { if (STREQ(STR(request), FLUSH_REQ_ADD)) { site = vstring_alloc(10); queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 2 && mail_queue_id_ok(STR(queue_id))) status = flush_add_service(STR(site), STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_SITE)) { site = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_SITE, site), ATTR_TYPE_END) == 1) status = flush_send_service(STR(site), UNTHROTTLE_BEFORE); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND_FILE)) { queue_id = vstring_alloc(10); if (attr_scan(client_stream, ATTR_FLAG_STRICT, RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), ATTR_TYPE_END) == 1) status = flush_send_file_service(STR(queue_id)); attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_REFRESH) || STREQ(STR(request), wakeup)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(var_fflush_refresh); } else if (STREQ(STR(request), FLUSH_REQ_PURGE)) { attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, FLUSH_STAT_OK), ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(0); } } else attr_print(client_stream, ATTR_FLAG_NONE, SEND_ATTR_INT(MAIL_ATTR_STATUS, status), ATTR_TYPE_END); vstring_free(request); if (site) vstring_free(site); if (queue_id) vstring_free(queue_id); }
static int flush_send_path(const char *path, int how) { const char *myname = "flush_send_path"; VSTRING *queue_id; VSTRING *queue_file; VSTREAM *log; struct utimbuf tbuf; static char qmgr_flush_trigger[] = { QMGR_REQ_FLUSH_DEAD, /* flush dead site/transport cache */ }; static char qmgr_scan_trigger[] = { QMGR_REQ_SCAN_INCOMING, /* scan incoming queue */ }; HTABLE *dup_filter; int count; /* * Sanity check. */ if (!mail_queue_id_ok(path)) return (FLUSH_STAT_BAD); /* * Open the logfile. If the file does not exist, then there is no queued * mail for this destination. */ if ((log = mail_queue_open(MAIL_QUEUE_FLUSH, path, O_RDWR, 0600)) == 0) { if (errno != ENOENT) msg_fatal("%s: open fast flush logfile %s: %m", myname, path); return (FLUSH_STAT_OK); } /* * We must lock the logfile, so that we don't lose information when it is * truncated. Unfortunately, this means that the file can be locked for a * significant amount of time. If things really get stuck the Postfix * watchdog will take care of it. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_EXCLUSIVE) < 0) msg_fatal("%s: lock fast flush logfile %s: %m", myname, path); /* * With the UNTHROTTLE_BEFORE strategy, we ask the queue manager to * unthrottle all transports and queues before we move a deferred queue * file to the incoming queue. This minimizes a race condition where the * queue manager seizes a queue file before it knows that we want to * flush that message. * * This reduces the race condition time window to a very small amount (the * flush server does not really know when the queue manager reads its * command fifo). But there is a worse race, where the queue manager * moves a deferred queue file to the active queue before we have a * chance to expedite its delivery. */ if (how & UNTHROTTLE_BEFORE) mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_flush_trigger, sizeof(qmgr_flush_trigger)); /* * This is the part that dominates running time: schedule the listed * queue files for delivery by updating their file time stamps and by * moving them from the deferred queue to the incoming queue. This should * take no more than a couple seconds under normal conditions. Filter out * duplicate queue file names to avoid hammering the file system, with * some finite limit on the amount of memory that we are willing to * sacrifice for duplicate filtering. Graceful degradation. * * By moving selected queue files from the deferred queue to the incoming * queue we optimize for the case where most deferred mail is for other * sites. If that assumption does not hold, i.e. all deferred mail is for * the same site, then doing a "fast flush" will cost more disk I/O than * a "slow flush" that delivers the entire deferred queue. This penalty * is only temporary - it will go away after we unite the active queue * and the incoming queue. */ queue_id = vstring_alloc(10); queue_file = vstring_alloc(10); dup_filter = htable_create(10); tbuf.actime = tbuf.modtime = event_time(); for (count = 0; vstring_get_nonl(queue_id, log) != VSTREAM_EOF; count++) { if (!mail_queue_id_ok(STR(queue_id))) { msg_warn("bad queue id \"%.30s...\" in fast flush logfile %s", STR(queue_id), path); continue; } if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE || htable_find(dup_filter, STR(queue_id)) == 0) { if (msg_verbose) msg_info("%s: logfile %s: update queue file %s time stamps", myname, path, STR(queue_id)); if (dup_filter->used <= FLUSH_DUP_FILTER_SIZE) htable_enter(dup_filter, STR(queue_id), 0); count += flush_one_file(STR(queue_id), queue_file, &tbuf, how); } else { if (msg_verbose) msg_info("%s: logfile %s: skip queue file %s as duplicate", myname, path, STR(queue_file)); } } htable_free(dup_filter, (void (*) (void *)) 0); vstring_free(queue_file); vstring_free(queue_id); /* * Truncate the fast flush log. */ if (count > 0 && ftruncate(vstream_fileno(log), (off_t) 0) < 0) msg_fatal("%s: truncate fast flush logfile %s: %m", myname, path); /* * Workaround for noatime mounts. Use futimes() if available. */ (void) utimes(VSTREAM_PATH(log), (struct timeval *) 0); /* * Request delivery and clean up. */ if (myflock(vstream_fileno(log), INTERNAL_LOCK, MYFLOCK_OP_NONE) < 0) msg_fatal("%s: unlock fast flush logfile %s: %m", myname, path); if (vstream_fclose(log) != 0) msg_warn("%s: read fast flush logfile %s: %m", myname, path); if (count > 0) { if (msg_verbose) msg_info("%s: requesting delivery for logfile %s", myname, path); mail_trigger(MAIL_CLASS_PUBLIC, var_queue_service, qmgr_scan_trigger, sizeof(qmgr_scan_trigger)); } return (FLUSH_STAT_OK); }
int main(int argc, char **argv) { VSTRING *buffer; VSTREAM *fp; int ch; int fd; struct stat st; int flags = 0; static char *queue_names[] = { MAIL_QUEUE_MAILDROP, MAIL_QUEUE_INCOMING, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_DEFERRED, MAIL_QUEUE_HOLD, MAIL_QUEUE_SAVED, 0, }; char **cpp; int tries; /* * Fingerprint executables and core dumps. */ MAIL_VERSION_STAMP_ALLOCATE; /* * To minimize confusion, make sure that the standard file descriptors * are open before opening anything else. XXX Work around for 44BSD where * fstat can return EBADF on an open file descriptor. */ for (fd = 0; fd < 3; fd++) if (fstat(fd, &st) == -1 && (close(fd), open("/dev/null", O_RDWR, 0)) != fd) msg_fatal("open /dev/null: %m"); /* * Set up logging. */ msg_vstream_init(argv[0], VSTREAM_ERR); /* * Parse JCL. */ while ((ch = GETOPT(argc, argv, "bc:dehoqv")) > 0) { switch (ch) { case 'b': flags |= PC_FLAG_PRINT_BODY; break; case 'c': if (setenv(CONF_ENV_PATH, optarg, 1) < 0) msg_fatal("out of memory"); break; case 'd': flags |= PC_FLAG_PRINT_RTYPE_DEC; break; case 'e': flags |= PC_FLAG_PRINT_ENV; break; case 'h': flags |= PC_FLAG_PRINT_HEADER; break; case 'o': flags |= PC_FLAG_PRINT_OFFSET; break; case 'q': flags |= PC_FLAG_SEARCH_QUEUE; break; case 'v': msg_verbose++; break; default: usage(argv[0]); } } if ((flags & PC_MASK_PRINT_ALL) == 0) flags |= PC_MASK_PRINT_ALL; /* * Further initialization... */ mail_conf_read(); /* * Initialize. */ buffer = vstring_alloc(10); /* * If no file names are given, copy stdin. */ if (argc == optind) { vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); postcat(VSTREAM_IN, buffer, flags); } /* * Copy the named queue files in the specified order. */ else if (flags & PC_FLAG_SEARCH_QUEUE) { if (chdir(var_queue_dir)) msg_fatal("chdir %s: %m", var_queue_dir); while (optind < argc) { if (!mail_queue_id_ok(argv[optind])) msg_fatal("bad mail queue ID: %s", argv[optind]); for (fp = 0, tries = 0; fp == 0 && tries < 2; tries++) for (cpp = queue_names; fp == 0 && *cpp != 0; cpp++) fp = mail_queue_open(*cpp, argv[optind], O_RDONLY, 0); if (fp == 0) msg_fatal("open queue file %s: %m", argv[optind]); postcat(fp, buffer, flags); if (vstream_fclose(fp)) msg_warn("close %s: %m", argv[optind]); optind++; } } /* * Copy the named files in the specified order. */ else { while (optind < argc) { if ((fp = vstream_fopen(argv[optind], O_RDONLY, 0)) == 0) msg_fatal("open %s: %m", argv[optind]); postcat(fp, buffer, flags); if (vstream_fclose(fp)) msg_warn("close %s: %m", argv[optind]); optind++; } } /* * Clean up. */ vstring_free(buffer); exit(0); }
static int bounce_one_proto(char *service_name, VSTREAM *client) { const char *myname = "bounce_one_proto"; int flags; int dsn_ret; /* * Read and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, &dsn_ret, ATTR_TYPE_FUNC, rcpb_scan, (void *) rcpt_buf, ATTR_TYPE_FUNC, dsb_scan, (void *) dsn_buf, ATTR_TYPE_END) != 9) { msg_warn("malformed request"); return (-1); } /* * Sanitize input. */ if (strcmp(service_name, MAIL_SERVICE_BOUNCE) != 0) { msg_warn("wrong service name \"%s\" for one-recipient bouncing", service_name); return (-1); } if (mail_queue_name_ok(STR(queue_name)) == 0) { msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } printable(STR(dsn_envid), '?'); VS_NEUTER(rcpt_buf->address); VS_NEUTER(rcpt_buf->orig_addr); VS_NEUTER(rcpt_buf->dsn_orcpt); VS_NEUTER(dsn_buf->status); VS_NEUTER(dsn_buf->action); VS_NEUTER(dsn_buf->reason); VS_NEUTER(dsn_buf->dtype); VS_NEUTER(dsn_buf->dtext); VS_NEUTER(dsn_buf->mtype); VS_NEUTER(dsn_buf->mname); (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); (void) DSN_FROM_DSN_BUF(dsn_buf); /* * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and * RECIPIENT_FROM_RCPT_BUF(). */ if (msg_verbose) msg_info("%s: flags=0x%x queue=%s id=%s encoding=%s sender=%s envid=%s dsn_ret=0x%x orig_to=%s to=%s off=%ld dsn_orig=%s notif=0x%x stat=%s act=%s why=%s", myname, flags, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(dsn_envid), dsn_ret, STR(rcpt_buf->orig_addr), STR(rcpt_buf->address), rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt), rcpt_buf->dsn_notify, STR(dsn_buf->status), STR(dsn_buf->action), STR(dsn_buf->reason)); /* * Execute the request. */ return (bounce_one_service(flags, STR(queue_name), STR(queue_id), STR(encoding), STR(sender), STR(dsn_envid), dsn_ret, rcpt_buf, dsn_buf, bounce_templates)); }
static int bounce_append_proto(char *service_name, VSTREAM *client) { const char *myname = "bounce_append_proto"; int flags; /* * Read and validate the client request. */ if (mail_command_server(client, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_FUNC, rcpb_scan, (void *) rcpt_buf, ATTR_TYPE_FUNC, dsb_scan, (void *) dsn_buf, ATTR_TYPE_END) != 4) { msg_warn("malformed request"); return (-1); } /* * Sanitize input. */ if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } VS_NEUTER(rcpt_buf->address); VS_NEUTER(rcpt_buf->orig_addr); VS_NEUTER(rcpt_buf->dsn_orcpt); VS_NEUTER(dsn_buf->status); VS_NEUTER(dsn_buf->action); VS_NEUTER(dsn_buf->reason); VS_NEUTER(dsn_buf->dtype); VS_NEUTER(dsn_buf->dtext); VS_NEUTER(dsn_buf->mtype); VS_NEUTER(dsn_buf->mname); (void) RECIPIENT_FROM_RCPT_BUF(rcpt_buf); (void) DSN_FROM_DSN_BUF(dsn_buf); /* * Beware: some DSN or RECIPIENT fields may be null; access dsn_buf and * rcpt_buf buffers instead. See DSN_FROM_DSN_BUF() and * RECIPIENT_FROM_RCPT_BUF(). */ if (msg_verbose) msg_info("%s: flags=0x%x service=%s id=%s org_to=%s to=%s off=%ld dsn_org=%s, notif=0x%x stat=%s act=%s why=%s", myname, flags, service_name, STR(queue_id), STR(rcpt_buf->orig_addr), STR(rcpt_buf->address), rcpt_buf->offset, STR(rcpt_buf->dsn_orcpt), rcpt_buf->dsn_notify, STR(dsn_buf->status), STR(dsn_buf->action), STR(dsn_buf->reason)); /* * On request by the client, set up a trap to delete the log file in case * of errors. */ if (flags & BOUNCE_FLAG_CLEAN) bounce_cleanup_register(service_name, STR(queue_id)); /* * Execute the request. */ return (bounce_append_service(flags, service_name, STR(queue_id), &rcpt_buf->rcpt, &dsn_buf->dsn)); }
static int bounce_verp_proto(char *service_name, VSTREAM *client) { const char *myname = "bounce_verp_proto"; int flags; int smtputf8; int dsn_ret; /* * Read and validate the client request. */ if (mail_command_server(client, RECV_ATTR_INT(MAIL_ATTR_FLAGS, &flags), RECV_ATTR_STR(MAIL_ATTR_QUEUE, queue_name), RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id), RECV_ATTR_STR(MAIL_ATTR_ENCODING, encoding), RECV_ATTR_INT(MAIL_ATTR_SMTPUTF8, &smtputf8), RECV_ATTR_STR(MAIL_ATTR_SENDER, sender), RECV_ATTR_STR(MAIL_ATTR_DSN_ENVID, dsn_envid), RECV_ATTR_INT(MAIL_ATTR_DSN_RET, &dsn_ret), RECV_ATTR_STR(MAIL_ATTR_VERPDL, verp_delims), ATTR_TYPE_END) != 9) { msg_warn("malformed request"); return (-1); } /* * Sanitize input. */ if (mail_queue_name_ok(STR(queue_name)) == 0) { msg_warn("malformed queue name: %s", printable(STR(queue_name), '?')); return (-1); } if (mail_queue_id_ok(STR(queue_id)) == 0) { msg_warn("malformed queue id: %s", printable(STR(queue_id), '?')); return (-1); } VS_NEUTER(encoding); VS_NEUTER(sender); VS_NEUTER(dsn_envid); VS_NEUTER(verp_delims); if (strlen(STR(verp_delims)) != 2) { msg_warn("malformed verp delimiter string: %s", STR(verp_delims)); return (-1); } if (msg_verbose) msg_info("%s: flags=0x%x service=%s queue=%s id=%s encoding=%s smtputf8=%d sender=%s envid=%s ret=0x%x delim=%s", myname, flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), smtputf8, STR(sender), STR(dsn_envid), dsn_ret, STR(verp_delims)); /* * On request by the client, set up a trap to delete the log file in case * of errors. */ if (flags & BOUNCE_FLAG_CLEAN) bounce_cleanup_register(service_name, STR(queue_id)); /* * Execute the request. Fall back to traditional notification if a bounce * was returned as undeliverable, because we don't want to VERPify those. */ if (!*STR(sender) || !strcasecmp_utf8(STR(sender), mail_addr_double_bounce())) { msg_warn("request to send VERP-style notification of bounced mail"); return (bounce_notify_service(flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), smtputf8, STR(sender), STR(dsn_envid), dsn_ret, bounce_templates)); } else return (bounce_notify_verp(flags, service_name, STR(queue_name), STR(queue_id), STR(encoding), smtputf8, STR(sender), STR(dsn_envid), dsn_ret, STR(verp_delims), bounce_templates)); }