static void pickup_service(char *unused_buf, ssize_t unused_len, char *unused_service, char **argv) { SCAN_DIR *scan; char *queue_name; PICKUP_INFO info; const char *path; char *id; int file_count; /* * Sanity check. This service takes no command-line arguments. */ if (argv[0]) msg_fatal("unexpected command-line argument: %s", argv[0]); /* * Skip over things that we don't want to open, such as files that are * still being written, or garbage. Leave it up to the sysadmin to remove * garbage. Keep scanning the queue directory until we stop removing * files from it. * * When we find a file, stroke the watchdog so that it will not bark while * some application is keeping us busy by injecting lots of mail into the * maildrop directory. */ queue_name = MAIL_QUEUE_MAILDROP; /* XXX should be a list */ do { file_count = 0; scan = scan_dir_open(queue_name); while ((id = scan_dir_next(scan)) != 0) { if (mail_open_ok(queue_name, id, &info.st, &path) == MAIL_OPEN_YES) { pickup_init(&info); info.path = mystrdup(path); watchdog_pat(); if (pickup_file(&info) == REMOVE_MESSAGE_FILE) { if (REMOVE(info.path)) msg_warn("remove %s: %m", info.path); else file_count++; } pickup_free(&info); } } scan_dir_close(scan); } while (file_count); }
static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) { const char *path; struct stat st; /* * A delivery agent marks a queue file as corrupt by changing its * attributes, and by pretending that delivery was deferred. */ if (message->flags && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) { qmgr_active_corrupt(message->queue_id); qmgr_message_free(message); return; } /* * If we did not read all recipients from this file, go read some more, * but remember whether some recipients have to be tried again. * * Throwing away queue files seems bad, especially when they made it this * far into the mail system. Therefore we save bad files to a separate * directory for further inspection by a human being. */ if (message->rcpt_offset > 0) { if (qmgr_message_realloc(message) == 0) { qmgr_active_corrupt(message->queue_id); qmgr_message_free(message); } else { if (message->refcount == 0) qmgr_active_done(message); /* recurse for consistency */ } return; } /* * XXX With multi-recipient mail, some recipients may have NOTIFY=SUCCESS * and others not. Depending on what subset of recipients are delivered, * a trace file may or may not be created. Even when the last partial * delivery attempt had no NOTIFY=SUCCESS recipients, a trace file may * still exist from a previous partial delivery attempt. So as long as * any recipient has NOTIFY=SUCCESS we have to always look for the trace * file and be prepared for the file not to exist. * * See also comments in bounce/bounce_notify_util.c. */ if ((message->tflags & (DEL_REQ_FLAG_USR_VRFY | DEL_REQ_FLAG_RECORD)) || (message->rflags & QMGR_READ_FLAG_NOTIFY_SUCCESS)) { atrace_flush(message->tflags, message->queue_name, message->queue_id, message->encoding, message->sender, message->dsn_envid, message->dsn_ret, qmgr_active_done_25_trace_flush, (char *) message); return; } /* * Asynchronous processing does not reach this point. */ qmgr_active_done_25_generic(message); }
int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) { const char *myname = "qmgr_active_feed"; QMGR_MESSAGE *message; struct stat st; const char *path; if (strcmp(scan_info->queue, MAIL_QUEUE_ACTIVE) == 0) msg_panic("%s: bad queue %s", myname, scan_info->queue); if (msg_verbose) msg_info("%s: queue %s", myname, scan_info->queue); /* * Make sure this is something we are willing to open. */ if (mail_open_ok(scan_info->queue, queue_id, &st, &path) == MAIL_OPEN_NO) return (0); if (msg_verbose) msg_info("%s: %s", myname, path); /* * Skip files that have time stamps into the future. They need to cool * down. Incoming and deferred files can have future time stamps. */ if ((scan_info->flags & QMGR_SCAN_ALL) == 0 && st.st_mtime > time((time_t *) 0) + 1) { if (msg_verbose) msg_info("%s: skip %s (%ld seconds)", myname, queue_id, (long) (st.st_mtime - event_time())); return (0); } /* * Move the message to the active queue. File access errors are fatal. */ if (mail_queue_rename(queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE)) { if (errno != ENOENT) msg_fatal("%s: %s: rename from %s to %s: %m", myname, queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); msg_warn("%s: %s: rename from %s to %s: %m", myname, queue_id, scan_info->queue, MAIL_QUEUE_ACTIVE); return (0); } /* * Extract envelope information: sender and recipients. At this point, * mail addresses have been processed by the cleanup service so they * should be in canonical form. Generate requests to deliver this * message. * * Throwing away queue files seems bad, especially when they made it this * far into the mail system. Therefore we save bad files to a separate * directory for further inspection. * * After queue manager restart it is possible that a queue file is still * being delivered. In that case (the file is locked), defer delivery by * a minimal amount of time. */ #define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP) if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ? scan_info->flags | QMGR_FLUSH_AFTER : scan_info->flags, (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ? st.st_mode & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0)) == 0) { qmgr_active_corrupt(queue_id); return (0); } else if (message == QMGR_MESSAGE_LOCKED) { qmgr_active_defer(MAIL_QUEUE_ACTIVE, queue_id, MAIL_QUEUE_INCOMING, 60); return (0); } else { /* * Special case if all recipients were already delivered. Send any * bounces and clean up. */ if (message->refcount == 0) qmgr_active_done(message); return (1); } }
static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request) { const char *myname = "deliver_request_get"; const char *path; struct stat st; static VSTRING *queue_name; static VSTRING *queue_id; static VSTRING *nexthop; static VSTRING *encoding; static VSTRING *address; static VSTRING *client_name; static VSTRING *client_addr; static VSTRING *client_port; static VSTRING *client_proto; static VSTRING *client_helo; static VSTRING *sasl_method; static VSTRING *sasl_username; static VSTRING *sasl_sender; static VSTRING *rewrite_context; static VSTRING *dsn_envid; static RCPT_BUF *rcpt_buf; int rcpt_count; int dsn_ret; /* * Initialize. For some reason I wanted to allow for multiple instances * of a deliver_request structure, thus the hoopla with string * initialization and copying. */ if (queue_name == 0) { queue_name = vstring_alloc(10); queue_id = vstring_alloc(10); nexthop = vstring_alloc(10); encoding = vstring_alloc(10); address = vstring_alloc(10); client_name = vstring_alloc(10); client_addr = vstring_alloc(10); client_port = vstring_alloc(10); client_proto = vstring_alloc(10); client_helo = vstring_alloc(10); sasl_method = vstring_alloc(10); sasl_username = vstring_alloc(10); sasl_sender = vstring_alloc(10); rewrite_context = vstring_alloc(10); dsn_envid = vstring_alloc(10); rcpt_buf = rcpb_create(); } /* * Extract the queue file name, data offset, and sender address. Abort * the conversation when they send bad information. */ if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request->flags, ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue_name, ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, &request->data_offset, ATTR_TYPE_LONG, MAIL_ATTR_SIZE, &request->data_size, ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, ATTR_TYPE_STR, MAIL_ATTR_ENCODING, encoding, ATTR_TYPE_STR, MAIL_ATTR_SENDER, address, ATTR_TYPE_STR, MAIL_ATTR_DSN_ENVID, dsn_envid, ATTR_TYPE_INT, MAIL_ATTR_DSN_RET, &dsn_ret, ATTR_TYPE_FUNC, msg_stats_scan, (void *) &request->msg_stats, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_NAME, client_name, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_ADDR, client_addr, ATTR_TYPE_STR, MAIL_ATTR_LOG_CLIENT_PORT, client_port, ATTR_TYPE_STR, MAIL_ATTR_LOG_PROTO_NAME, client_proto, ATTR_TYPE_STR, MAIL_ATTR_LOG_HELO_NAME, client_helo, /* XXX Should be encapsulated with ATTR_TYPE_FUNC. */ ATTR_TYPE_STR, MAIL_ATTR_SASL_METHOD, sasl_method, ATTR_TYPE_STR, MAIL_ATTR_SASL_USERNAME, sasl_username, ATTR_TYPE_STR, MAIL_ATTR_SASL_SENDER, sasl_sender, /* XXX Ditto if we want to pass TLS certificate info. */ ATTR_TYPE_STR, MAIL_ATTR_RWR_CONTEXT, rewrite_context, ATTR_TYPE_INT, MAIL_ATTR_RCPT_COUNT, &rcpt_count, ATTR_TYPE_END) != 21) { msg_warn("%s: error receiving common attributes", myname); return (-1); } if (mail_open_ok(vstring_str(queue_name), vstring_str(queue_id), &st, &path) == 0) return (-1); /* Don't override hand-off time after deliver_pass() delegation. */ if (request->msg_stats.agent_handoff.tv_sec == 0) GETTIMEOFDAY(&request->msg_stats.agent_handoff); request->queue_name = mystrdup(vstring_str(queue_name)); request->queue_id = mystrdup(vstring_str(queue_id)); request->nexthop = mystrdup(vstring_str(nexthop)); request->encoding = mystrdup(vstring_str(encoding)); request->sender = mystrdup(vstring_str(address)); request->client_name = mystrdup(vstring_str(client_name)); request->client_addr = mystrdup(vstring_str(client_addr)); request->client_port = mystrdup(vstring_str(client_port)); request->client_proto = mystrdup(vstring_str(client_proto)); request->client_helo = mystrdup(vstring_str(client_helo)); request->sasl_method = mystrdup(vstring_str(sasl_method)); request->sasl_username = mystrdup(vstring_str(sasl_username)); request->sasl_sender = mystrdup(vstring_str(sasl_sender)); request->rewrite_context = mystrdup(vstring_str(rewrite_context)); request->dsn_envid = mystrdup(vstring_str(dsn_envid)); request->dsn_ret = dsn_ret; /* * Extract the recipient offset and address list. Skip over any * attributes from the sender that we do not understand. */ while (rcpt_count-- > 0) { if (attr_scan(stream, ATTR_FLAG_STRICT, ATTR_TYPE_FUNC, rcpb_scan, (void *) rcpt_buf, ATTR_TYPE_END) != 1) { msg_warn("%s: error receiving recipient attributes", myname); return (-1); } recipient_list_add(&request->rcpt_list, rcpt_buf->offset, vstring_str(rcpt_buf->dsn_orcpt), rcpt_buf->dsn_notify, vstring_str(rcpt_buf->orig_addr), vstring_str(rcpt_buf->address)); } if (request->rcpt_list.len <= 0) { msg_warn("%s: no recipients in delivery request for destination %s", request->queue_id, request->nexthop); return (-1); } /* * Open the queue file and set a shared lock, in order to prevent * duplicate deliveries when the queue is flushed immediately after queue * manager restart. * * The queue manager locks the file exclusively when it enters the active * queue, and releases the lock before starting deliveries from that * file. The queue manager does not lock the file again when reading more * recipients into memory. When the queue manager is restarted, the new * process moves files from the active queue to the incoming queue to cool * off for a while. Delivery agents should therefore never try to open a * file that is locked by a queue manager process. * * Opening the queue file can fail for a variety of reasons, such as the * system running out of resources. Instead of throwing away mail, we're * raising a fatal error which forces the mail system to back off, and * retry later. */ #define DELIVER_LOCK_MODE (MYFLOCK_OP_SHARED | MYFLOCK_OP_NOWAIT) request->fp = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); if (request->fp == 0) { if (errno != ENOENT) msg_fatal("open %s %s: %m", request->queue_name, request->queue_id); msg_warn("open %s %s: %m", request->queue_name, request->queue_id); return (-1); } if (msg_verbose) msg_info("%s: file %s", myname, VSTREAM_PATH(request->fp)); if (myflock(vstream_fileno(request->fp), INTERNAL_LOCK, DELIVER_LOCK_MODE) < 0) msg_fatal("shared lock %s: %m", VSTREAM_PATH(request->fp)); close_on_exec(vstream_fileno(request->fp), CLOSE_ON_EXEC); return (0); }
static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) { char *myname = "qmgr_active_done_2_generic"; const char *path; struct stat st; int status; /* * A delivery agent marks a queue file as corrupt by changing its * attributes, and by pretending that delivery was deferred. */ if (message->flags && mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path) == MAIL_OPEN_NO) { qmgr_active_corrupt(message->queue_id); qmgr_message_free(message); return; } /* * If we did not read all recipients from this file, go read some more, * but remember whether some recipients have to be tried again. * * Throwing away queue files seems bad, especially when they made it this * far into the mail system. Therefore we save bad files to a separate * directory for further inspection by a human being. */ if (message->rcpt_offset > 0) { if (qmgr_message_realloc(message) == 0) { qmgr_active_corrupt(message->queue_id); qmgr_message_free(message); } else { if (message->refcount == 0) qmgr_active_done(message); /* recurse for consistency */ } return; } /* * As a temporary implementation, synchronously inform the sender of * trace information. This will block for 10 seconds when the qmgr FIFO * is full. */ if (message->tflags & (DEL_REQ_FLAG_EXPAND | DEL_REQ_FLAG_RECORD)) { status = trace_flush(message->tflags, message->queue_name, message->queue_id, message->encoding, message->sender); if (status == 0 && message->tflags_offset) qmgr_message_kill_record(message, message->tflags_offset); message->flags |= status; } /* * If we get to this point we have tried all recipients for this message. * If the message is too old, try to bounce it. * * Bounces are sent asynchronously to avoid stalling while the cleanup * daemon waits for the qmgr to accept the "new mail" trigger. */ if (message->flags) { if (event_time() >= message->arrival_time + (*message->sender ? var_max_queue_time : var_dsn_queue_time)) { msg_info("%s: from=<%s>, status=expired, returned to sender", message->queue_id, message->sender); if (message->verp_delims == 0 || var_verp_bounce_off) adefer_flush(BOUNCE_FLAG_KEEP, message->queue_name, message->queue_id, message->encoding, message->errors_to, qmgr_active_done_3_defer_flush, (char *) message); else adefer_flush_verp(BOUNCE_FLAG_KEEP, message->queue_name, message->queue_id, message->encoding, message->errors_to, message->verp_delims, qmgr_active_done_3_defer_flush, (char *) message); return; } else if (message->warn_time > 0 && event_time() > message->warn_time) { if (msg_verbose) msg_info("%s: sending defer warning for %s", myname, message->queue_id); adefer_warn(BOUNCE_FLAG_KEEP, message->queue_name, message->queue_id, message->encoding, message->errors_to, qmgr_active_done_3_defer_warn, (char *) message); return; } } /* * Asynchronous processing does not reach this point. */ qmgr_active_done_3_generic(message); }