diff options
| author | perry <perry@NetBSD.org> | 2002-02-02 23:10:24 +0000 |
|---|---|---|
| committer | perry <perry@NetBSD.org> | 2002-02-02 23:10:24 +0000 |
| commit | 6f59dc7aebbf705da79e61ef3fd24b48ed99d327 (patch) | |
| tree | c6e9fd356c8aac78b966a9dd53f5d0d14d5e62cd /gnu/dist/postfix/src | |
| parent | ecf43984517b4cfb837e78958769cc8207ea5288 (diff) | |
Postfix 1.1.2
(Postfix releases are now numbered -- 1.1.2 means 1.1, patchlevel 2.)
Lots of new features, same great security.
Diffstat (limited to 'gnu/dist/postfix/src')
270 files changed, 18000 insertions, 1355 deletions
diff --git a/gnu/dist/postfix/src/bounce/Makefile.in b/gnu/dist/postfix/src/bounce/Makefile.in index 4172afd76ce..66066c2330f 100644 --- a/gnu/dist/postfix/src/bounce/Makefile.in +++ b/gnu/dist/postfix/src/bounce/Makefile.in @@ -1,8 +1,8 @@ SHELL = /bin/sh SRCS = bounce.c bounce_append_service.c bounce_notify_service.c \ - bounce_cleanup.c bounce_notify_util.c + bounce_cleanup.c bounce_notify_util.c bounce_notify_verp.c OBJS = bounce.o bounce_append_service.o bounce_notify_service.o \ - bounce_cleanup.o bounce_notify_util.o + bounce_cleanup.o bounce_notify_util.o bounce_notify_verp.o HDRS = TESTSRC = WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -65,10 +65,12 @@ bounce.o: ../../include/vstream.h bounce.o: ../../include/stringops.h bounce.o: ../../include/mail_proto.h bounce.o: ../../include/iostuff.h +bounce.o: ../../include/attr.h bounce.o: ../../include/mail_queue.h bounce.o: ../../include/mail_params.h bounce.o: ../../include/mail_conf.h bounce.o: ../../include/bounce.h +bounce.o: ../../include/mail_addr.h bounce.o: ../../include/mail_server.h bounce.o: bounce_service.h bounce.o: ../../include/bounce_log.h @@ -133,3 +135,19 @@ bounce_notify_util.o: ../../include/name_mask.h bounce_notify_util.o: ../../include/bounce_log.h bounce_notify_util.o: ../../include/mail_date.h bounce_notify_util.o: bounce_service.h +bounce_notify_verp.o: bounce_notify_verp.c +bounce_notify_verp.o: ../../include/sys_defs.h +bounce_notify_verp.o: ../../include/msg.h +bounce_notify_verp.o: ../../include/vstream.h +bounce_notify_verp.o: ../../include/vbuf.h +bounce_notify_verp.o: ../../include/name_mask.h +bounce_notify_verp.o: ../../include/mail_params.h +bounce_notify_verp.o: ../../include/mail_queue.h +bounce_notify_verp.o: ../../include/vstring.h +bounce_notify_verp.o: ../../include/post_mail.h +bounce_notify_verp.o: ../../include/cleanup_user.h +bounce_notify_verp.o: ../../include/mail_addr.h +bounce_notify_verp.o: ../../include/mail_error.h +bounce_notify_verp.o: ../../include/verp_sender.h +bounce_notify_verp.o: bounce_service.h +bounce_notify_verp.o: ../../include/bounce_log.h diff --git a/gnu/dist/postfix/src/bounce/bounce.c b/gnu/dist/postfix/src/bounce/bounce.c index eeedb7b141b..3cb98cc0028 100644 --- a/gnu/dist/postfix/src/bounce/bounce.c +++ b/gnu/dist/postfix/src/bounce/bounce.c @@ -80,6 +80,11 @@ /* System library. */ #include <sys_defs.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif /* Utility library. */ @@ -95,6 +100,7 @@ #include <mail_params.h> #include <mail_conf.h> #include <bounce.h> +#include <mail_addr.h> /* Single-threaded server skeleton. */ @@ -122,6 +128,7 @@ static VSTRING *queue_id; static VSTRING *queue_name; static VSTRING *recipient; static VSTRING *sender; +static VSTRING *verp_delims; static VSTRING *why; #define STR vstring_str @@ -135,8 +142,12 @@ static int bounce_append_proto(char *service_name, VSTREAM *client) /* * Read the and validate the client request. */ - if (mail_command_read(client, "%d %s %s %s", - &flags, queue_id, recipient, why) != 4) { + 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_RECIP, recipient, + ATTR_TYPE_STR, MAIL_ATTR_WHY, why, + ATTR_TYPE_END) != 4) { msg_warn("malformed request"); return (-1); } @@ -171,8 +182,12 @@ static int bounce_notify_proto(char *service_name, VSTREAM *client, int flush) /* * Read and validate the client request. */ - if (mail_command_read(client, "%d %s %s %s", - &flags, queue_name, queue_id, sender) != 4) { + 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_SENDER, sender, + ATTR_TYPE_END) != 4) { msg_warn("malformed request"); return (-1); } @@ -202,6 +217,65 @@ static int bounce_notify_proto(char *service_name, VSTREAM *client, int flush) STR(queue_id), STR(sender), flush)); } +/* bounce_verp_proto - bounce_notify server protocol, VERP style */ + +static int bounce_verp_proto(char *service_name, VSTREAM *client, int flush) +{ + 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_SENDER, sender, + ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp_delims, + ATTR_TYPE_END) != 5) { + 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: service=%s queue=%s id=%s sender=%s delim=%s", + myname, service_name, STR(queue_name), STR(queue_id), + 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(service_name, STR(queue_name), + STR(queue_id), STR(sender), flush)); + } else + return (bounce_notify_verp(service_name, STR(queue_name), + STR(queue_id), STR(sender), + STR(verp_delims), flush)); +} + /* bounce_service - parse bounce command type and delegate */ static void bounce_service(VSTREAM *client, char *service_name, char **argv) @@ -225,9 +299,12 @@ static void bounce_service(VSTREAM *client, char *service_name, char **argv) #define REALLY_BOUNCE 1 #define JUST_WARN 0 - if (mail_scan(client, "%d", &command) != 1) { + if (attr_scan(client, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, &command, 0) != 1) { msg_warn("malformed request"); status = -1; + } else if (command == BOUNCE_CMD_VERP) { + status = bounce_verp_proto(service_name, client, REALLY_BOUNCE); } else if (command == BOUNCE_CMD_FLUSH) { status = bounce_notify_proto(service_name, client, REALLY_BOUNCE); } else if (command == BOUNCE_CMD_WARN) { @@ -243,7 +320,9 @@ static void bounce_service(VSTREAM *client, char *service_name, char **argv) * When the request has completed, send the completion status to the * client. */ - mail_print(client, "%d", status); + attr_print(client, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_END); vstream_fflush(client); /* @@ -271,6 +350,7 @@ static void post_jail_init(char *unused_name, char **unused_argv) queue_name = vstring_alloc(10); recipient = vstring_alloc(10); sender = vstring_alloc(10); + verp_delims = vstring_alloc(10); why = vstring_alloc(10); } diff --git a/gnu/dist/postfix/src/bounce/bounce_notify_service.c b/gnu/dist/postfix/src/bounce/bounce_notify_service.c index 35e10a45cb9..284b6468ee5 100644 --- a/gnu/dist/postfix/src/bounce/bounce_notify_service.c +++ b/gnu/dist/postfix/src/bounce/bounce_notify_service.c @@ -138,8 +138,7 @@ int bounce_notify_service(char *service, char *queue_name, postmaster = flush ? var_2bounce_rcpt : var_delay_rcpt; if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), postmaster, - NULL_CLEANUP_FLAGS, - "BOUNCE")) != 0) { + NULL_CLEANUP_FLAGS)) != 0) { /* * Double bounce to Postmaster. This is the last opportunity @@ -163,8 +162,7 @@ int bounce_notify_service(char *service, char *queue_name, */ else { if ((bounce = post_mail_fopen_nowait(NULL_SENDER, recipient, - NULL_CLEANUP_FLAGS, - "BOUNCE")) != 0) { + NULL_CLEANUP_FLAGS)) != 0) { /* * Send the bounce message header, some boilerplate text that @@ -203,8 +201,7 @@ int bounce_notify_service(char *service, char *queue_name, postmaster = flush ? var_bounce_rcpt : var_delay_rcpt; if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), postmaster, - NULL_CLEANUP_FLAGS, - "BOUNCE")) != 0) { + NULL_CLEANUP_FLAGS)) != 0) { if (bounce_header(bounce, bounce_info, postmaster) == 0 && bounce_diagnostic_log(bounce, bounce_info) == 0 && bounce_header_dsn(bounce, bounce_info) == 0 diff --git a/gnu/dist/postfix/src/bounce/bounce_notify_util.c b/gnu/dist/postfix/src/bounce/bounce_notify_util.c index cc5e15f6bf0..f2dc04156d9 100644 --- a/gnu/dist/postfix/src/bounce/bounce_notify_util.c +++ b/gnu/dist/postfix/src/bounce/bounce_notify_util.c @@ -199,7 +199,7 @@ BOUNCE_INFO *bounce_mail_init(const char *service, const char *queue_name, */ if ((bounce_info->log_handle = bounce_log_open(bounce_info->service, bounce_info->queue_id, - O_RDONLY, 0)) == 0 + O_RDWR, 0)) == 0 && errno != ENOENT) msg_fatal("open %s %s: %m", bounce_info->service, bounce_info->queue_id); @@ -222,14 +222,16 @@ BOUNCE_INFO *bounce_mail_init(const char *service, const char *queue_name, * corrupted just send whatever we can (remember this is a best effort, * it does not have to be perfect). */ - while ((rec_type = rec_get(bounce_info->orig_fp, - bounce_info->buf, 0)) > 0) { - if (rec_type == REC_TYPE_TIME && bounce_info->arrival_time == 0) { - if ((bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0) - bounce_info->arrival_time = 0; - } else if (rec_type == REC_TYPE_MESG) { - bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp); - break; + if (bounce_info->orig_fp != 0) { + while ((rec_type = rec_get(bounce_info->orig_fp, + bounce_info->buf, 0)) > 0) { + if (rec_type == REC_TYPE_TIME && bounce_info->arrival_time == 0) { + if ((bounce_info->arrival_time = atol(STR(bounce_info->buf))) < 0) + bounce_info->arrival_time = 0; + } else if (rec_type == REC_TYPE_MESG) { + bounce_info->orig_offs = vstream_ftell(bounce_info->orig_fp); + break; + } } } return (bounce_info); @@ -339,7 +341,7 @@ int bounce_boilerplate(VSTREAM *bounce, BOUNCE_INFO *bounce_info) "####################################################################"); post_mail_fputs(bounce, ""); post_mail_fprintf(bounce, - "Your message could not be delivered for %.1f hours.", + "Your message could not be delivered for %.1f hours.", var_delay_warn_time / 3600.0); post_mail_fprintf(bounce, "It will be retried until it is %.1f days old.", @@ -478,7 +480,7 @@ int bounce_recipient_dsn(VSTREAM *bounce, BOUNCE_INFO *bounce_info) #endif if (bounce_info->flush == 0) post_mail_fprintf(bounce, "Will-Retry-Until: %s", - mail_date(bounce_info->arrival_time + var_max_queue_time)); + mail_date(bounce_info->arrival_time + var_max_queue_time)); return (vstream_ferror(bounce)); } @@ -547,12 +549,16 @@ int bounce_original(VSTREAM *bounce, BOUNCE_INFO *bounce_info, if (var_bounce_limit == 0 || bounce_length < var_bounce_limit) { bounce_length += VSTRING_LEN(bounce_info->buf) + 2; status = (REC_PUT_BUF(bounce, rec_type, bounce_info->buf) != rec_type); - } + } else + break; } /* * Final MIME headers. These require -- at the end of the boundary * string. + * + * XXX This should be a separate bounce_terminate() entry so we can be + * assured that the terminator will always be sent. */ post_mail_fputs(bounce, ""); post_mail_fprintf(bounce, "--%s--", bounce_info->mime_boundary); diff --git a/gnu/dist/postfix/src/bounce/bounce_notify_verp.c b/gnu/dist/postfix/src/bounce/bounce_notify_verp.c new file mode 100644 index 00000000000..f1880bae00c --- /dev/null +++ b/gnu/dist/postfix/src/bounce/bounce_notify_verp.c @@ -0,0 +1,210 @@ +/*++ +/* NAME +/* bounce_notify_verp 3 +/* SUMMARY +/* send non-delivery report to sender, server side +/* SYNOPSIS +/* #include "bounce_service.h" +/* +/* int bounce_notify_verp(service, queue_name, queue_id, sender, +/* verp_delims, flush) +/* char *queue_name; +/* char *queue_id; +/* char *sender; +/* char *verp_delims; +/* int flush; +/* DESCRIPTION +/* This module implements the server side of the bounce_notify() +/* (send bounce message) request. If flush is zero, the logfile +/* is not removed, and a warning is sent instead of a bounce. +/* The bounce recipient address is encoded in VERP format. +/* This routine must be used for single bounces only. +/* +/* When a message bounces, a full copy is sent to the originator, +/* and an optional copy of the diagnostics with message headers is +/* sent to the postmaster. The result is non-zero when the operation +/* should be tried again. +/* +/* When a bounce is sent, the sender address is the empty +/* address. +/* DIAGNOSTICS +/* Fatal error: error opening existing file. Warnings: corrupt +/* message file. A corrupt message is saved to the "corrupt" +/* queue for further inspection. +/* SEE ALSO +/* bounce(3) basic bounce service client interface +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <name_mask.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_queue.h> +#include <post_mail.h> +#include <mail_addr.h> +#include <mail_error.h> +#include <verp_sender.h> + +/* Application-specific. */ + +#include "bounce_service.h" + +#define STR vstring_str + +/* bounce_notify_verp - send a bounce */ + +int bounce_notify_verp(char *service, char *queue_name, + char *queue_id, char *recipient, + char *verp_delims, int flush) +{ + char *myname = "bounce_notify_verp"; + BOUNCE_INFO *bounce_info; + int bounce_status = 0; + int postmaster_status; + VSTREAM *bounce; + int notify_mask = name_mask(VAR_NOTIFY_CLASSES, mail_error_masks, + var_notify_classes); + char *postmaster; + VSTRING *verp_buf = vstring_alloc(100); + + /* + * Sanity checks. We must be called only for undeliverable non-bounce + * messages. + */ + if (*recipient == 0) + msg_panic("%s: attempt to bounce a single bounce", myname); + if (strcasecmp(recipient, mail_addr_double_bounce()) == 0) + msg_panic("%s: attempt to bounce a double bounce", myname); + + /* + * Initialize. Open queue file, bounce log, etc. + */ + bounce_info = bounce_mail_init(service, queue_name, queue_id, flush); + +#define NULL_SENDER MAIL_ADDR_EMPTY /* special address */ +#define NULL_CLEANUP_FLAGS 0 +#define BOUNCE_HEADERS 1 +#define BOUNCE_ALL 0 + + /* + * A non-bounce message was returned. Send a single bounce, one per + * recipient. + */ + while (bounce_log_read(bounce_info->log_handle) != 0) { + + /* + * Notify the originator. + */ + verp_sender(verp_buf, verp_delims, recipient, + bounce_info->log_handle->recipient); + if ((bounce = post_mail_fopen_nowait(NULL_SENDER, STR(verp_buf), + NULL_CLEANUP_FLAGS)) != 0) { + + /* + * Send the bounce message header, some boilerplate text that + * pretends that we are a polite mail system, the text with + * reason for the bounce, and a copy of the original message. + */ + if (bounce_header(bounce, bounce_info, STR(verp_buf)) == 0 + && bounce_boilerplate(bounce, bounce_info) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, flush ? + BOUNCE_ALL : BOUNCE_HEADERS); + bounce_status = post_mail_fclose(bounce); + } else + bounce_status = 1; + + /* + * Stop at the first sign of trouble, instead of making the problem + * worse. + */ + if (bounce_status != 0) + break; + + /* + * Mark this recipient as done. + */ + bounce_log_delrcpt(bounce_info->log_handle); + + /* + * Optionally, send a postmaster notice. + * + * This postmaster notice is not critical, so if it fails don't + * retransmit the bounce that we just generated, just log a warning. + */ +#define WANT_IF_BOUNCE (flush == 1 && (notify_mask & MAIL_ERROR_BOUNCE)) +#define WANT_IF_DELAY (flush == 0 && (notify_mask & MAIL_ERROR_DELAY)) + + if (WANT_IF_BOUNCE || WANT_IF_DELAY) { + + /* + * Send the text with reason for the bounce, and the headers of + * the original message. Don't bother sending the boiler-plate + * text. This postmaster notice is not critical, so if it fails + * don't retransmit the bounce that we just generated, just log a + * warning. + */ + postmaster = flush ? var_bounce_rcpt : var_delay_rcpt; + if ((bounce = post_mail_fopen_nowait(mail_addr_double_bounce(), + postmaster, + NULL_CLEANUP_FLAGS)) != 0) { + if (bounce_header(bounce, bounce_info, postmaster) == 0 + && bounce_recipient_log(bounce, bounce_info) == 0 + && bounce_header_dsn(bounce, bounce_info) == 0 + && bounce_recipient_dsn(bounce, bounce_info) == 0) + bounce_original(bounce, bounce_info, BOUNCE_HEADERS); + postmaster_status = post_mail_fclose(bounce); + } else + postmaster_status = 1; + + if (postmaster_status) + msg_warn("postmaster notice failed while bouncing to %s", + recipient); + } + } + + /* + * Examine the completion status. Delete the bounce log file only when + * the bounce was posted successfully, and only if we are bouncing for + * real, not just warning. + */ + if (flush != 0 && bounce_status == 0 && mail_queue_remove(service, queue_id) + && errno != ENOENT) + msg_fatal("remove %s %s: %m", service, queue_id); + + /* + * Cleanup. + */ + bounce_mail_free(bounce_info); + vstring_free(verp_buf); + + return (bounce_status); +} diff --git a/gnu/dist/postfix/src/bounce/bounce_service.h b/gnu/dist/postfix/src/bounce/bounce_service.h index 369a3da0d08..858609b7970 100644 --- a/gnu/dist/postfix/src/bounce/bounce_service.h +++ b/gnu/dist/postfix/src/bounce/bounce_service.h @@ -29,6 +29,11 @@ extern int bounce_append_service(char *, char *, char *, char *); extern int bounce_notify_service(char *, char *, char *, char *, int); /* + * bounce_notify_verp.c + */ +extern int bounce_notify_verp(char *, char *, char *, char *, char *, int); + + /* * bounce_cleanup.c */ extern VSTRING *bounce_cleanup_path; @@ -51,7 +56,7 @@ typedef struct { VSTREAM *orig_fp; /* open queue file */ long orig_offs; /* start of content */ time_t arrival_time; /* time of arrival */ - BOUNCE_LOG *log_handle; /* open logfile */ + BOUNCE_LOG *log_handle; /* open logfile */ } BOUNCE_INFO; extern BOUNCE_INFO *bounce_mail_init(const char *, const char *, const char *, int); diff --git a/gnu/dist/postfix/src/cleanup/Makefile.in b/gnu/dist/postfix/src/cleanup/Makefile.in index dd13667a6fc..cec834caa00 100644 --- a/gnu/dist/postfix/src/cleanup/Makefile.in +++ b/gnu/dist/postfix/src/cleanup/Makefile.in @@ -72,6 +72,7 @@ cleanup.o: ../../include/mail_conf.h cleanup.o: ../../include/cleanup_user.h cleanup.o: ../../include/mail_proto.h cleanup.o: ../../include/iostuff.h +cleanup.o: ../../include/attr.h cleanup.o: ../../include/mail_params.h cleanup.o: ../../include/record.h cleanup.o: ../../include/rec_type.h @@ -93,6 +94,7 @@ cleanup_api.o: ../../include/mail_queue.h cleanup_api.o: ../../include/vstream.h cleanup_api.o: ../../include/mail_proto.h cleanup_api.o: ../../include/iostuff.h +cleanup_api.o: ../../include/attr.h cleanup_api.o: ../../include/bounce.h cleanup_api.o: ../../include/mail_params.h cleanup_api.o: ../../include/mail_stream.h @@ -120,6 +122,7 @@ cleanup_envelope.o: ../../include/mail_params.h cleanup_envelope.o: ../../include/ext_prop.h cleanup_envelope.o: ../../include/mail_addr.h cleanup_envelope.o: ../../include/canon_addr.h +cleanup_envelope.o: ../../include/verp_sender.h cleanup_envelope.o: cleanup.h cleanup_envelope.o: ../../include/argv.h cleanup_envelope.o: ../../include/maps.h @@ -152,6 +155,7 @@ cleanup_init.o: cleanup_init.c cleanup_init.o: ../../include/sys_defs.h cleanup_init.o: ../../include/msg.h cleanup_init.o: ../../include/iostuff.h +cleanup_init.o: ../../include/name_mask.h cleanup_init.o: ../../include/mail_addr.h cleanup_init.o: ../../include/mail_params.h cleanup_init.o: ../../include/ext_prop.h diff --git a/gnu/dist/postfix/src/cleanup/cleanup.c b/gnu/dist/postfix/src/cleanup/cleanup.c index 70476aa2ec2..74c80bcc635 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup.c +++ b/gnu/dist/postfix/src/cleanup/cleanup.c @@ -35,7 +35,7 @@ /* header addresses (i.e. strip host or domain information below /* all domains listed in the \fBmasquerade_domains\fR parameter, /* except for user names listed in \fBmasquerade_exceptions\fR). -/* Address masquerading does not affect envelope recipients. +/* By default, address masquerading does not affect envelope recipients. /* .IP \(bu /* Optionally, expand envelope recipients according to information /* found in the \fBvirtual\fR(5) lookup tables. @@ -94,6 +94,10 @@ /* .IP \fBsender_canonical_maps\fR /* Address mapping lookup table for envelope and header sender /* addresses. +/* .IP \fBmasquerade_classes\fR +/* List of address classes subject to masquerading: zero or +/* more of \fBenvelope_sender\fR, \fBenvelope_recipient\fR, +/* \fBheader_sender\fR, \fBheader_recipient\fR. /* .IP \fBmasquerade_domains\fR /* List of domains that hide their subdomain structure. /* .IP \fBmasquerade_exceptions\fR @@ -107,6 +111,9 @@ /* Limit the number of envelope recipients that are remembered. /* .IP \fBheader_size_limit\fR /* Limit the amount of memory in bytes used to process a message header. +/* .IP \fBin_flow_delay\fR +/* Amount of time to pause before accepting a message, when the +/* message arrival rate exceeds the message delivery rate. /* .IP \fBextract_recipient_limit\fR /* Limit the amount of recipients extracted from message headers. /* SEE ALSO @@ -184,8 +191,12 @@ static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) * can't read the client processing options we can pretty much forget * about the whole operation. */ - mail_print(src, "%s", state->queue_id); - if (mail_scan(src, "%d", &flags) != 1) { + attr_print(src, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, state->queue_id, + ATTR_TYPE_END); + if (attr_scan(src, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &flags, + ATTR_TYPE_END) != 1) { state->errs |= CLEANUP_STAT_BAD; flags = 0; } @@ -223,7 +234,12 @@ static void cleanup_service(VSTREAM *src, char *unused_service, char **argv) /* * Finish this message, and report the result status to the client. */ - mail_print(src, "%d", cleanup_close(state)); + attr_print(src, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, cleanup_flush(state), + ATTR_TYPE_STR, MAIL_ATTR_WHY, state->reason ? + state->reason : "", + ATTR_TYPE_END); + cleanup_free(state); /* * Cleanup. @@ -271,5 +287,6 @@ int main(int argc, char **argv) MAIL_SERVER_PRE_INIT, cleanup_pre_jail, MAIL_SERVER_POST_INIT, cleanup_post_jail, MAIL_SERVER_PRE_ACCEPT, pre_accept, + MAIL_SERVER_IN_FLOW_DELAY, 0); } diff --git a/gnu/dist/postfix/src/cleanup/cleanup.h b/gnu/dist/postfix/src/cleanup/cleanup.h index a1e56848c73..09d700ed18a 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup.h +++ b/gnu/dist/postfix/src/cleanup/cleanup.h @@ -59,6 +59,7 @@ typedef struct CLEANUP_STATE { off_t xtra_offset; /* start of extra segment */ int end_seen; /* REC_TYPE_END seen */ int rcpt_count; /* recipient count */ + char *reason; /* failure reason */ } CLEANUP_STATE; /* @@ -71,6 +72,15 @@ extern MAPS *cleanup_header_checks; extern MAPS *cleanup_body_checks; extern MAPS *cleanup_virtual_maps; extern ARGV *cleanup_masq_domains; +extern int cleanup_masq_flags; + + /* + * Address masquerading fine control. + */ +#define CLEANUP_MASQ_FLAG_ENV_FROM (1<<0) /* envelope sender */ +#define CLEANUP_MASQ_FLAG_ENV_RCPT (1<<1) /* envelope recipient */ +#define CLEANUP_MASQ_FLAG_HDR_FROM (1<<2) /* header sender */ +#define CLEANUP_MASQ_FLAG_HDR_RCPT (1<<3) /* header recipient */ /* * Restrictions on extension propagation. @@ -94,7 +104,8 @@ extern void cleanup_state_free(CLEANUP_STATE *); */ extern CLEANUP_STATE *cleanup_open(void); extern void cleanup_control(CLEANUP_STATE *, int); -extern int cleanup_close(CLEANUP_STATE *); +extern int cleanup_flush(CLEANUP_STATE *); +extern void cleanup_free(CLEANUP_STATE *); extern void cleanup_all(void); extern void cleanup_pre_jail(char *, char **); extern void cleanup_post_jail(char *, char **); diff --git a/gnu/dist/postfix/src/cleanup/cleanup_api.c b/gnu/dist/postfix/src/cleanup/cleanup_api.c index 0773d2cf81f..2da603b9109 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_api.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_api.c @@ -18,7 +18,10 @@ /* char *buf; /* int len; /* -/* int cleanup_close(state) +/* int cleanup_flush(state) +/* CLEANUP_STATE *state; +/* +/* int cleanup_free(state) /* CLEANUP_STATE *state; /* DESCRIPTION /* This module implements a callable interface to the cleanup service @@ -27,26 +30,39 @@ /* /* cleanup_open() creates a new queue file and performs other /* per-message initialization. The result is a handle that should be -/* given to the cleanup_control(), cleanup_record() and cleanup_close() -/* routines. The name of the queue file is in the queue_id result -/* structure member. +/* given to the cleanup_control(), cleanup_record(), cleanup_flush() +/* and cleanup_free() routines. The name of the queue file is in the +/* queue_id result structure member. /* /* cleanup_control() processes per-message flags specified by the caller. /* These flags control the handling of data errors, and must be set /* before processing the first message record. -/* +/* .IP CLEANUP_FLAG_BOUNCE +/* The cleanup server is responsible for returning undeliverable +/* mail (too many hops, message too large) to the sender. +/* .IP CLEANUP_FLAG_FILTER +/* Enable header/body filtering. This should be enabled only with mail +/* that enters Postfix, not with locally forwarded mail or with bounce +/* messages. +/* .IP CLEANUP_FLAG_EXTRACT +/* Extract recipients from message headers when no recipients are +/* provided in the message envelope records. +/* .PP /* CLEANUP_RECORD() is a macro that processes one message record, /* that copies the result to the queue file, and that maintains a /* little state machine. The last record in a valid message has type /* REC_TYPE_END. In order to find out if a message is corrupted, /* the caller is encouraged to test the CLEANUP_OUT_OK(state) macro. /* The result is false when further message processing is futile. -/* In that case, it is safe to call cleanup_close() immediately. +/* In that case, it is safe to call cleanup_flush() immediately. /* -/* cleanup_close() finishes a queue file. In case of any errors, +/* cleanup_flush() closes a queue file. In case of any errors, /* the file is removed. The result value is non-zero in case of -/* problems. Use cleanup_strerror() to translate the result into -/* human_readable text. +/* problems. In some cases a human-readable text can be found in +/* the state->reason member. In all other cases, use cleanup_strerror() +/* to translate the result into human-readable text. +/* +/* cleanup_free() destroys its argument. /* DIAGNOSTICS /* Problems and transactions are logged to \fBsyslogd\fR(8). /* SEE ALSO @@ -109,7 +125,7 @@ CLEANUP_STATE *cleanup_open(void) * that the runtime error handler can clean up in case of problems. */ state->handle = mail_stream_file(MAIL_QUEUE_INCOMING, - MAIL_CLASS_PUBLIC, MAIL_SERVICE_QUEUE); + MAIL_CLASS_PUBLIC, MAIL_SERVICE_QUEUE, 0); state->dst = state->handle->stream; cleanup_path = mystrdup(VSTREAM_PATH(state->dst)); state->queue_id = mystrdup(state->handle->id); @@ -148,25 +164,28 @@ void cleanup_control(CLEANUP_STATE *state, int flags) * definition. */ if ((state->flags = flags) & CLEANUP_FLAG_BOUNCE) { - state->err_mask = - (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_SIZE); + state->err_mask = CLEANUP_STAT_MASK_INCOMPLETE; } else { - state->err_mask = CLEANUP_STAT_LETHAL; + state->err_mask = ~CLEANUP_STAT_MASK_EXTRACT_RCPT; } } -/* cleanup_close - finish queue file */ +/* cleanup_flush - finish queue file */ -int cleanup_close(CLEANUP_STATE *state) +int cleanup_flush(CLEANUP_STATE *state) { char *junk; int status; /* - * See if there are any errors. For example, the message is incomplete, - * or it needs to be bounced for lack of recipients. We want to turn on - * the execute bits on a file only when we really want the queue manager - * to process it. + * Ignore recipient extraction alarms if (a) we did (not need to) extract + * recipients, or (b) we did not examine all queue file records. + */ + if (state->recip != 0 || CLEANUP_OUT_OK(state) == 0) + state->errs &= ~CLEANUP_STAT_MASK_EXTRACT_RCPT; + + /* + * Raise these errors only if we examined all queue file records. */ if (CLEANUP_OUT_OK(state)) { if (state->recip == 0) @@ -180,10 +199,11 @@ int cleanup_close(CLEANUP_STATE *state) * because we are about to tell the sender that it can throw away its * copy of the message. */ - if ((state->errs & CLEANUP_STAT_LETHAL) == 0) - state->errs |= mail_stream_finish(state->handle); - else + if (state->errs == 0) { + state->errs = mail_stream_finish(state->handle, (VSTRING *) 0); + } else { mail_stream_cleanup(state->handle); + } state->handle = 0; state->dst = 0; @@ -200,26 +220,19 @@ int cleanup_close(CLEANUP_STATE *state) * * Do not log the arrival of a message that will be bounced by the client. * - * XXX CLEANUP_STAT_LETHAL masks errors that are not directly fatal (e.g., - * header buffer overflow is normally allowed to happen), but that can - * indirectly become a problem (e.g., no recipients were extracted from - * message headers because we could not process all the message headers). - * However, cleanup_strerror() prioritizes errors so that it can report - * the cause (e.g., header buffer overflow), which is more useful. - * * XXX When bouncing, should log sender because qmgr won't be able to. */ #define CAN_BOUNCE() \ - ((state->errs & (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE)) == 0 \ + ((state->errs & CLEANUP_STAT_MASK_CANT_BOUNCE) == 0 \ && state->sender != 0 \ && (state->flags & CLEANUP_FLAG_BOUNCE) != 0) - if (state->errs & CLEANUP_STAT_LETHAL) { + if (state->errs != 0) { if (CAN_BOUNCE()) { if (bounce_append(BOUNCE_FLAG_CLEAN, state->queue_id, state->recip ? state->recip : "unknown", "cleanup", state->time, - "Message processing aborted: %s", + "%s", state->reason ? state->reason : cleanup_strerror(state->errs)) == 0 && bounce_flush(BOUNCE_FLAG_CLEAN, MAIL_QUEUE_INCOMING, state->queue_id, state->sender) == 0) { @@ -249,8 +262,14 @@ int cleanup_close(CLEANUP_STATE *state) * initializations at the beginning of cleanup_open(). */ if (msg_verbose) - msg_info("cleanup_close: status %d", state->errs); - status = state->errs & CLEANUP_STAT_LETHAL; - cleanup_state_free(state); + msg_info("cleanup_flush: status %d", state->errs); + status = state->errs; return (status); } + +/* cleanup_free - pay the last respects */ + +void cleanup_free(CLEANUP_STATE *state) +{ + cleanup_state_free(state); +} diff --git a/gnu/dist/postfix/src/cleanup/cleanup_envelope.c b/gnu/dist/postfix/src/cleanup/cleanup_envelope.c index 86758c146ad..f4fd30e75d0 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_envelope.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_envelope.c @@ -67,6 +67,7 @@ #include <ext_prop.h> #include <mail_addr.h> #include <canon_addr.h> +#include <verp_sender.h> /* Application-specific. */ @@ -148,7 +149,8 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type, char *buf, if (cleanup_comm_canon_maps) cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); - if (cleanup_masq_domains) + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_FROM)) cleanup_masquerade_internal(clean_addr, cleanup_masq_domains); CLEANUP_OUT_BUF(state, type, clean_addr); if (state->sender == 0) @@ -170,6 +172,9 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type, char *buf, if (cleanup_comm_canon_maps) cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT)) + cleanup_masquerade_internal(clean_addr, cleanup_masq_domains); cleanup_out_recipient(state, STR(clean_addr)); if (state->recip == 0) state->recip = mystrdup(STR(clean_addr)); @@ -179,6 +184,18 @@ static void cleanup_envelope_process(CLEANUP_STATE *state, int type, char *buf, state->errs |= CLEANUP_STAT_BAD; return; } + } else if (type == REC_TYPE_VERP) { + if (state->sender == 0 || *state->sender == 0) { + state->errs |= CLEANUP_STAT_BAD; + return; + } + if (verp_delims_verify(buf) == 0) { + cleanup_out(state, type, buf, len); + } else { + msg_warn("%s: bad VERP delimiters: \"%s\"", state->queue_id, buf); + state->errs |= CLEANUP_STAT_BAD; + return; + } } else { cleanup_out(state, type, buf, len); } diff --git a/gnu/dist/postfix/src/cleanup/cleanup_extracted.c b/gnu/dist/postfix/src/cleanup/cleanup_extracted.c index 243404be1c4..b9721bd88e3 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_extracted.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_extracted.c @@ -123,6 +123,9 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type, char *buf, if (cleanup_comm_canon_maps) cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT)) + cleanup_masquerade_internal(clean_addr, cleanup_masq_domains); cleanup_out_recipient(state, STR(clean_addr)); if (state->recip == 0) state->recip = mystrdup(STR(clean_addr)); @@ -150,8 +153,8 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type, char *buf, if (rcpt->argc >= var_extra_rcpt_limit) { state->errs |= CLEANUP_STAT_ROVFL; } else { + clean_addr = vstring_alloc(100); if (*var_always_bcc && rcpt->argv[0]) { - clean_addr = vstring_alloc(100); cleanup_rewrite_internal(clean_addr, var_always_bcc); if (cleanup_rcpt_canon_maps) cleanup_map11_internal(state, clean_addr, cleanup_rcpt_canon_maps, @@ -160,13 +163,26 @@ static void cleanup_extracted_process(CLEANUP_STATE *state, int type, char *buf, cleanup_map11_internal(state, clean_addr, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); argv_add(rcpt, STR(clean_addr), (char *) 0); - vstring_free(clean_addr); } argv_terminate(rcpt); - for (cpp = rcpt->argv; CLEANUP_OUT_OK(state) && *cpp; cpp++) - cleanup_out_recipient(state, *cpp); + + /* + * Recipients extracted from message headers already have + * undergone recipient address rewriting (see cleanup_message.c), + * but still may need address masquerading. + */ + for (cpp = rcpt->argv; CLEANUP_OUT_OK(state) && *cpp; cpp++) { + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_ENV_RCPT)) { + vstring_strcpy(clean_addr, *cpp); + cleanup_masquerade_internal(clean_addr, cleanup_masq_domains); + cleanup_out_recipient(state, STR(clean_addr)); + } else + cleanup_out_recipient(state, *cpp); + } if (rcpt->argv[0]) state->recip = mystrdup(rcpt->argv[0]); + vstring_free(clean_addr); } } diff --git a/gnu/dist/postfix/src/cleanup/cleanup_init.c b/gnu/dist/postfix/src/cleanup/cleanup_init.c index 0cb5812c5bc..b3e7a0157e4 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_init.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_init.c @@ -69,6 +69,7 @@ #include <msg.h> #include <iostuff.h> +#include <name_mask.h> /* Global library. */ @@ -106,6 +107,7 @@ char *var_prop_extension; /* propagate unmatched extension */ char *var_always_bcc; /* big brother */ int var_extra_rcpt_limit; /* recipient extract limit */ char *var_rcpt_witheld; /* recipients not disclosed */ +char *var_masq_classes; /* what to masquerade */ CONFIG_INT_TABLE cleanup_int_table[] = { VAR_HOPCOUNT_LIMIT, DEF_HOPCOUNT_LIMIT, &var_hopcount_limit, 1, 0, @@ -133,6 +135,7 @@ CONFIG_STR_TABLE cleanup_str_table[] = { VAR_PROP_EXTENSION, DEF_PROP_EXTENSION, &var_prop_extension, 0, 0, VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0, VAR_RCPT_WITHELD, DEF_RCPT_WITHELD, &var_rcpt_witheld, 1, 0, + VAR_MASQ_CLASSES, DEF_MASQ_CLASSES, &var_masq_classes, 0, 0, 0, }; @@ -146,6 +149,7 @@ MAPS *cleanup_header_checks; MAPS *cleanup_body_checks; MAPS *cleanup_virtual_maps; ARGV *cleanup_masq_domains; +int cleanup_masq_flags; /* * Address extension propagation restrictions. @@ -164,9 +168,17 @@ void cleanup_all(void) void cleanup_pre_jail(char *unused_name, char **unused_argv) { + static NAME_MASK masq_class_table[] = { + MASQ_CLASS_ENV_FROM, CLEANUP_MASQ_FLAG_ENV_FROM, + MASQ_CLASS_ENV_RCPT, CLEANUP_MASQ_FLAG_ENV_RCPT, + MASQ_CLASS_HDR_FROM, CLEANUP_MASQ_FLAG_HDR_FROM, + MASQ_CLASS_HDR_RCPT, CLEANUP_MASQ_FLAG_HDR_RCPT, + 0, + }; + if (*var_canonical_maps) cleanup_comm_canon_maps = - maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, DICT_FLAG_LOCK); + maps_create(VAR_CANONICAL_MAPS, var_canonical_maps, DICT_FLAG_LOCK); if (*var_send_canon_maps) cleanup_send_canon_maps = maps_create(VAR_SEND_CANON_MAPS, var_send_canon_maps, @@ -186,6 +198,9 @@ void cleanup_pre_jail(char *unused_name, char **unused_argv) if (*var_body_checks) cleanup_body_checks = maps_create(VAR_BODY_CHECKS, var_body_checks, DICT_FLAG_LOCK); + if (*var_masq_classes) + cleanup_masq_flags = name_mask(VAR_MASQ_CLASSES, masq_class_table, + var_masq_classes); } /* cleanup_post_jail - initialize after entering the chroot jail */ diff --git a/gnu/dist/postfix/src/cleanup/cleanup_message.c b/gnu/dist/postfix/src/cleanup/cleanup_message.c index 99644203fb0..fca690451bc 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_message.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_message.c @@ -178,7 +178,8 @@ static void cleanup_rewrite_sender(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts) if (cleanup_comm_canon_maps) cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); - if (cleanup_masq_domains) + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_FROM)) cleanup_masquerade_tree(*tpp, cleanup_masq_domains); if (hdr_opts->type == HDR_FROM && state->from == 0) state->from = cleanup_extract_internal(state->header_buf, *tpp); @@ -228,6 +229,11 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts) if (cleanup_comm_canon_maps) cleanup_map11_tree(state, *tpp, cleanup_comm_canon_maps, cleanup_ext_prop_mask & EXT_PROP_CANONICAL); + + /* + * Extract envelope recipients after recipient address rewriting but + * before address masquerading. + */ if (state->recip == 0 && (hdr_opts->flags & HDR_OPT_EXTRACT) != 0) { rcpt = (hdr_opts->flags & HDR_OPT_RR) ? state->resent_recip : state->recipients; @@ -236,7 +242,8 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts) argv_add(rcpt, vstring_str(state->temp1), (char *) 0); } } - if (cleanup_masq_domains) + if (cleanup_masq_domains + && (cleanup_masq_flags & CLEANUP_MASQ_FLAG_HDR_RCPT)) cleanup_masquerade_tree(*tpp, cleanup_masq_domains); } vstring_sprintf(state->header_buf, "%s: ", hdr_opts->name); @@ -247,6 +254,33 @@ static void cleanup_rewrite_recip(CLEANUP_STATE *state, HEADER_OPTS *hdr_opts) cleanup_fold_header(state); } +/* cleanup_check_reject - parse and match header/body REJECT line */ + +static int cleanup_check_reject(CLEANUP_STATE *state, const char *value) +{ + const char *reason = value + strcspn(value, " \t"); + + /* + * See if they spelled REJECT right. + * + * XXX The reason should be set only if we have a more severe error than + * anything that was found before. This calls for a cleanup_set_error() + * routine that takes an error code and an optional text. + */ + if (strncasecmp(value, "REJECT", reason - value) == 0) { + if (state->reason == 0) { + while (*reason && ISSPACE(*reason)) + reason++; + state->reason = mystrdup(*reason ? reason : + cleanup_strerror(CLEANUP_STAT_CONT)); + } + state->errs |= CLEANUP_STAT_CONT; + return (1); + } else { + return (0); + } +} + /* cleanup_header - process one complete header line */ static void cleanup_header(CLEANUP_STATE *state) @@ -262,13 +296,17 @@ static void cleanup_header(CLEANUP_STATE *state) const char *value; if ((value = maps_find(cleanup_header_checks, header, 0)) != 0) { - if (strcasecmp(value, "REJECT") == 0) { - msg_info("%s: reject: header %.200s; from=<%s> to=<%s>", + if (cleanup_check_reject(state, value) != 0) { + msg_info("%s: reject: header %.200s; from=<%s> to=<%s>: %s", state->queue_id, header, state->sender, - state->recip ? state->recip : "unknown"); - state->errs |= CLEANUP_STAT_CONT; + state->recip ? state->recip : "unknown", + state->reason); } else if (strcasecmp(value, "IGNORE") == 0) { return; + } else if (strcasecmp(value, "WARN") == 0) { + msg_info("%s: warning: header %.200s; from=<%s> to=<%s>", + state->queue_id, header, state->sender, + state->recip ? state->recip : "unknown"); } } } @@ -287,6 +325,29 @@ static void cleanup_header(CLEANUP_STATE *state) * we should do with this header: delete, count, rewrite. Note that we * should examine headers even when they will be deleted from the output, * because the addresses in those headers might be needed elsewhere. + * + * XXX 2821: Return-path breakage. + * + * RFC 821 specifies: When the receiver-SMTP makes the "final delivery" of a + * message it inserts at the beginning of the mail data a return path + * line. The return path line preserves the information in the + * <reverse-path> from the MAIL command. Here, final delivery means the + * message leaves the SMTP world. Normally, this would mean it has been + * delivered to the destination user, but in some cases it may be further + * processed and transmitted by another mail system. + * + * And that is what Postfix implements. Delivery agents prepend + * Return-Path:. In order to avoid cluttering up the message with + * possibly inconsistent Return-Path: information (the sender can change + * as the result of mail forwarding or mailing list delivery), Postfix + * removes any existing Return-Path: headers. + * + * RFC 2821 Section 4.4 specifies: A message-originating SMTP system + * SHOULD NOT send a message that already contains a Return-path header. + * SMTP servers performing a relay function MUST NOT inspect the message + * data, and especially not to the extent needed to determine if + * Return-path headers are present. SMTP servers making final delivery + * MAY remove Return-path headers before adding their own. */ else { state->headers_seen |= (1 << hdr_opts->type); @@ -324,6 +385,11 @@ static void cleanup_missing_headers(CLEANUP_STATE *state) /* * Add a missing (Resent-)Message-Id: header. The message ID gives the * time in GMT units, plus the local queue ID. + * + * XXX Message-Id is not a required message header (RFC 822 and RFC 2822). + * + * XXX It is the queue ID non-inode bits that prevent messages from getting + * the same Message-Id within the same second. */ if ((state->headers_seen & (1 << (state->resent[0] ? HDR_RESENT_MESSAGE_ID : HDR_MESSAGE_ID))) == 0) { @@ -366,6 +432,29 @@ static void cleanup_missing_headers(CLEANUP_STATE *state) } /* + * XXX 2821: Appendix B: The return address in the MAIL command SHOULD, + * if possible, be derived from the system's identity for the submitting + * (local) user, and the "From:" header field otherwise. If there is a + * system identity available, it SHOULD also be copied to the Sender + * header field if it is different from the address in the From header + * field. (Any Sender field that was already there SHOULD be removed.) + * Similar wording appears in RFC 2822 section 3.6.2. + * + * Postfix presently does not insert a Sender: header if envelope and From: + * address differ. Older Postfix versions assumed that the envelope + * sender address specifies the system identity and inserted Sender: + * whenever envelope and From: differed. This was wrong with relayed + * mail, and was often not even desirable with original submissions. + * + * XXX 2822 Section 3.6.2, as well as RFC 822 Section 4.1: FROM headers can + * contain multiple addresses. If this is the case, then a Sender: header + * must be provided with a single address. + * + * Postfix does not count the number of addresses in a From: header + * (although doing so is trivial, once the address is parsed). + */ + + /* * Add a missing destination header. */ #define VISIBLE_RCPT ((1 << HDR_TO) | (1 << HDR_RESENT_TO) \ @@ -491,13 +580,17 @@ static void cleanup_message_body(CLEANUP_STATE *state, int type, char *buf, int const char *value; if ((value = maps_find(cleanup_body_checks, buf, 0)) != 0) { - if (strcasecmp(value, "REJECT") == 0) { - msg_info("%s: reject: body %.200s; from=<%s> to=<%s>", + if (cleanup_check_reject(state, value) != 0) { + msg_info("%s: reject: body %.200s; from=<%s> to=<%s>: %s", state->queue_id, buf, state->sender, - state->recip ? state->recip : "unknown"); - state->errs |= CLEANUP_STAT_CONT; + state->recip ? state->recip : "unknown", + state->reason); } else if (strcasecmp(value, "IGNORE") == 0) { return; + } else if (strcasecmp(value, "WARN") == 0) { + msg_info("%s: warning: body %.200s; from=<%s> to=<%s>", + state->queue_id, buf, state->sender, + state->recip ? state->recip : "unknown"); } } } diff --git a/gnu/dist/postfix/src/cleanup/cleanup_state.c b/gnu/dist/postfix/src/cleanup/cleanup_state.c index c7e5b74b6c2..2206d7a42ec 100644 --- a/gnu/dist/postfix/src/cleanup/cleanup_state.c +++ b/gnu/dist/postfix/src/cleanup/cleanup_state.c @@ -84,6 +84,7 @@ CLEANUP_STATE *cleanup_state_alloc(void) state->xtra_offset = -1; state->end_seen = 0; state->rcpt_count = 0; + state->reason = 0; return (state); } @@ -113,5 +114,7 @@ void cleanup_state_free(CLEANUP_STATE *state) if (state->queue_id) myfree(state->queue_id); been_here_free(state->dups); + if (state->reason) + myfree(state->reason); myfree((char *) state); } diff --git a/gnu/dist/postfix/src/dns/Makefile.in b/gnu/dist/postfix/src/dns/Makefile.in index e132be3db58..812c3fbc8e8 100644 --- a/gnu/dist/postfix/src/dns/Makefile.in +++ b/gnu/dist/postfix/src/dns/Makefile.in @@ -81,6 +81,7 @@ dns_rr.o: dns_rr.c dns_rr.o: ../../include/sys_defs.h dns_rr.o: ../../include/msg.h dns_rr.o: ../../include/mymalloc.h +dns_rr.o: ../../include/myrand.h dns_rr.o: dns.h dns_rr.o: ../../include/vstring.h dns_rr.o: ../../include/vbuf.h diff --git a/gnu/dist/postfix/src/dns/dns.h b/gnu/dist/postfix/src/dns/dns.h index 490a5ff3923..e08583d6e8d 100644 --- a/gnu/dist/postfix/src/dns/dns.h +++ b/gnu/dist/postfix/src/dns/dns.h @@ -105,6 +105,7 @@ extern void dns_rr_free(DNS_RR *); extern DNS_RR *dns_rr_copy(DNS_RR *); extern DNS_RR *dns_rr_append(DNS_RR *, DNS_RR *); extern DNS_RR *dns_rr_sort(DNS_RR *, int (*) (DNS_RR *, DNS_RR *)); +extern DNS_RR *dns_rr_shuffle(DNS_RR *); /* * dns_lookup.c diff --git a/gnu/dist/postfix/src/dns/dns_rr.c b/gnu/dist/postfix/src/dns/dns_rr.c index 39cbf192061..2dc8edd1b0c 100644 --- a/gnu/dist/postfix/src/dns/dns_rr.c +++ b/gnu/dist/postfix/src/dns/dns_rr.c @@ -26,6 +26,9 @@ /* DNS_RR *dns_rr_sort(list, compar) /* DNS_RR *list /* int (*compar)(DNS_RR *, DNS_RR *); +/* +/* DNS_RR *dns_rr_shuffle(list) +/* DNS_RR *list; /* DESCRIPTION /* The routines in this module maintain memory for DNS resource record /* information, and maintain lists of DNS resource records. @@ -49,6 +52,8 @@ /* dns_rr_sort() sorts a list of resource records into ascending /* order according to a user-specified criterion. The result is the /* sorted list. +/* +/* dns_rr_shuffle() randomly permutes a list of resource records. /* LICENSE /* .ad /* .fi @@ -70,6 +75,7 @@ #include <msg.h> #include <mymalloc.h> +#include <myrand.h> /* DNS library. */ @@ -193,3 +199,47 @@ DNS_RR *dns_rr_sort(DNS_RR *list, int (*compar) (DNS_RR *, DNS_RR *)) dns_rr_sort_user = saved_user; return (list); } + +/* dns_rr_shuffle - shuffle resource record list */ + +DNS_RR *dns_rr_shuffle(DNS_RR *list) +{ + DNS_RR **rr_array; + DNS_RR *rr; + int len; + int i; + int r; + + /* + * Build linear array with pointers to each list element. + */ + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + /* void */ ; + rr_array = (DNS_RR **) mymalloc(len * sizeof(*rr_array)); + for (len = 0, rr = list; rr != 0; len++, rr = rr->next) + rr_array[len] = rr; + + /* + * Shuffle resource records. + */ + for (i = 0; i < len; i++) { + r = myrand() % len; + rr = rr_array[i]; + rr_array[i] = rr_array[r]; + rr_array[r] = rr; + } + + /* + * Fix the links. + */ + for (i = 0; i < len - 1; i++) + rr_array[i]->next = rr_array[i + 1]; + rr_array[i]->next = 0; + list = rr_array[0]; + + /* + * Cleanup. + */ + myfree((char *) rr_array); + return (list); +} diff --git a/gnu/dist/postfix/src/flush/Makefile.in b/gnu/dist/postfix/src/flush/Makefile.in index 19b51de2809..835aea3787e 100644 --- a/gnu/dist/postfix/src/flush/Makefile.in +++ b/gnu/dist/postfix/src/flush/Makefile.in @@ -71,10 +71,14 @@ flush.o: ../../include/mail_params.h flush.o: ../../include/mail_queue.h flush.o: ../../include/mail_proto.h flush.o: ../../include/iostuff.h +flush.o: ../../include/attr.h flush.o: ../../include/mail_flush.h flush.o: ../../include/flush_clnt.h flush.o: ../../include/mail_conf.h flush.o: ../../include/mail_scan_dir.h flush.o: ../../include/maps.h flush.o: ../../include/domain_list.h +flush.o: ../../include/match_list.h +flush.o: ../../include/match_ops.h +flush.o: ../../include/match_parent_style.h flush.o: ../../include/mail_server.h diff --git a/gnu/dist/postfix/src/flush/flush.c b/gnu/dist/postfix/src/flush/flush.c index c8c76c0d09a..828681ad24a 100644 --- a/gnu/dist/postfix/src/flush/flush.c +++ b/gnu/dist/postfix/src/flush/flush.c @@ -38,7 +38,7 @@ /* if mail is undeliverable it will be added back to the logfile. /* .sp /* If the destination is not eligible for a fast flush logfile, -/* this request triggers delivery of all queued mail. +/* this request is rejected (see below for status codes). /* .IP \fBTRIGGER_REQ_WAKEUP\fR /* This wakeup request from the master is an alternative way to /* request \fBFLUSH_REQ_REFRESH\fR. @@ -66,6 +66,10 @@ /* request parameter value). /* .IP \fBFLUSH_STAT_FAIL\fR /* The request failed. +/* .IP \fBFLUSH_STAT_DENY\fR +/* The request was denied because the destination domain is not +/* eligible for fast flush service, or because the fast flush +/* service is disabled. /* SECURITY /* .ad /* .fi @@ -106,6 +110,10 @@ /* .IP \fBfast_flush_purge_time\fR /* Remove an empty "fast flush" logfile that was not updated in /* this amount of time (default time unit: days). +/* .IP \fBparent_domain_matches_subdomains\fR +/* List of Postfix features that use \fIdomain.name\fR patterns +/* to match \fIsub.domain.name\fR (as opposed to +/* requiring \fI.domain.name\fR patterns). /* SEE ALSO /* smtpd(8) Postfix SMTP server /* qmgr(8) Postfix queue manager @@ -156,6 +164,7 @@ #include <mail_scan_dir.h> #include <maps.h> #include <domain_list.h> +#include <match_parent_style.h> /* Single server skeleton. */ @@ -243,10 +252,10 @@ static int flush_add_service(const char *site, const char *queue_id) msg_info("%s: site %s queue_id %s", myname, site, queue_id); /* - * If this site is not eligible for logging, just ignore the request. + * If this site is not eligible for logging, deny the request. */ if (flush_policy_ok(site) == 0) - return (FLUSH_STAT_OK); + return (FLUSH_STAT_DENY); /* * Map site to path and update log. @@ -319,10 +328,10 @@ static int flush_send_service(const char *site) msg_info("%s: site %s", myname, site); /* - * If this site is not eligible for logging, deliver all queued mail. + * If this site is not eligible for logging, deny the request. */ if (flush_policy_ok(site) == 0) - return (mail_flush_deferred()); + return (FLUSH_STAT_DENY); /* * Map site name to path name and flush the log. @@ -507,6 +516,51 @@ static int flush_refresh_service(int max_age) return (FLUSH_STAT_OK); } +/* flush_request_receive - receive request */ + +static int flush_request_receive(VSTREAM *client_stream, VSTRING *request) +{ + int count; + + /* + * Kluge: choose the protocol depending on the request size. + */ + if (read_wait(vstream_fileno(client_stream), var_ipc_timeout) < 0) { + msg_warn("timeout while waiting for data from %s", + VSTREAM_PATH(client_stream)); + return (-1); + } + if ((count = peekfd(vstream_fileno(client_stream))) < 0) { + msg_warn("cannot examine read buffer of %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + + /* + * Short request: master trigger. Use the string+null protocol. + */ + if (count <= 2) { + if (vstring_get_null(request, client_stream) == VSTREAM_EOF) { + msg_warn("end-of-input while reading request from %s: %m", + VSTREAM_PATH(client_stream)); + return (-1); + } + } + + /* + * Long request: real flush client. Use the attribute list protocol. + */ + else { + if (attr_scan(client_stream, + ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_REQ, request, + ATTR_TYPE_END) != 1) { + return (-1); + } + } + return (0); +} + /* flush_service - perform service for client */ static void flush_service(VSTREAM *client_stream, char *unused_service, @@ -536,31 +590,46 @@ static void flush_service(VSTREAM *client_stream, char *unused_service, * All connection-management stuff is handled by the common code in * single_server.c. */ - if (mail_scan(client_stream, "%s", request) == 1) { + 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 (mail_command_read(client_stream, "%s %s", site, queue_id) == 2 + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_SITE, site, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, + ATTR_TYPE_END) == 2 && mail_queue_id_ok(STR(queue_id))) status = flush_add_service(lowercase(STR(site)), STR(queue_id)); - mail_print(client_stream, "%d", status); + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_SEND)) { site = vstring_alloc(10); - if (mail_command_read(client_stream, "%s", site) == 1) + if (attr_scan(client_stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_SITE, site, + ATTR_TYPE_END) == 1) status = flush_send_service(lowercase(STR(site))); - mail_print(client_stream, "%d", status); + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_END); } else if (STREQ(STR(request), FLUSH_REQ_REFRESH) || STREQ(STR(request), wakeup)) { - mail_print(client_stream, "%d", FLUSH_STAT_OK); + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, 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)) { - mail_print(client_stream, "%d", FLUSH_STAT_OK); + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, FLUSH_STAT_OK, + ATTR_TYPE_END); vstream_fflush(client_stream); (void) flush_refresh_service(0); } } else - mail_print(client_stream, "%d", status); + attr_print(client_stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_END); vstring_free(request); if (site) vstring_free(site); @@ -572,7 +641,8 @@ static void flush_service(VSTREAM *client_stream, char *unused_service, static void pre_jail_init(char *unused_name, char **unused_argv) { - flush_domains = domain_list_init(var_fflush_domains); + flush_domains = domain_list_init(match_parent_style(VAR_FFLUSH_DOMAINS), + var_fflush_domains); } /* main - pass control to the single-threaded skeleton */ diff --git a/gnu/dist/postfix/src/global/abounce.c b/gnu/dist/postfix/src/global/abounce.c index bfda481d51d..5cacccf5e8c 100644 --- a/gnu/dist/postfix/src/global/abounce.c +++ b/gnu/dist/postfix/src/global/abounce.c @@ -14,6 +14,15 @@ /* void (*callback)(int status, char *context); /* char *context; /* +/* void abounce_flush_verp(flags, queue, id, sender, verp, callback, context) +/* int flags; +/* const char *queue; +/* const char *id; +/* const char *sender; +/* const char *verp; +/* void (*callback)(int status, char *context); +/* char *context; +/* /* void adefer_flush(flags, queue, id, sender, callback, context) /* int flags; /* const char *queue; @@ -22,6 +31,15 @@ /* void (*callback)(int status, char *context); /* char *context; /* +/* void adefer_flush_verp(flags, queue, id, sender, verp, callback, context) +/* int flags; +/* const char *queue; +/* const char *id; +/* const char *sender; +/* const char *verp; +/* void (*callback)(int status, char *context); +/* char *context; +/* /* void adefer_warn(flags, queue, id, sender, callback, context) /* int flags; /* const char *queue; @@ -38,10 +56,16 @@ /* the specified sender, including the bounce log that was /* built with bounce_append(). /* +/* abounce_flush_verp() is like abounce_flush() but sends +/* one VERP style notification per undeliverable recipient. +/* /* adefer_flush() bounces the specified message to /* the specified sender, including the defer log that was /* built with defer_append(). /* +/* adefer_flush_verp() is like adefer_flush() but sends +/* one VERP style notification per undeliverable recipient. +/* /* adefer_warn() sends a "mail is delayed" notification to /* the specified sender, including the defer log that was /* built with defer_append(). @@ -64,6 +88,8 @@ /* file has the same name as the original message file. /* .IP sender /* The sender envelope address. +/* .IP verp +/* VERP delimiter characters. /* .IP callback /* Name of a routine that receives the notification status as /* documented for bounce_flush() or defer_flush(). @@ -145,7 +171,69 @@ static void abounce_event(int unused_event, char *context) int status; event_disable_readwrite(vstream_fileno(ap->fp)); - abounce_done(ap, mail_scan(ap->fp, "%d", &status) == 1 ? status : -1); + abounce_done(ap, attr_scan(ap->fp, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_END) == 1 ? status : -1); +} + +/* abounce_request_verp - suspend pseudo thread until server reply event */ + +static void abounce_request_verp(const char *class, const char *service, + int command, int flags, + const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, + char *context) +{ + ABOUNCE *ap; + + /* + * Save pseudo thread state. Connect to the server. Send the request and + * suspend the pseudo thread until the server replies (or dies). + */ + ap = (ABOUNCE *) mymalloc(sizeof(*ap)); + ap->command = command; + ap->flags = flags; + ap->id = mystrdup(id); + ap->callback = callback; + ap->context = context; + ap->fp = mail_connect_wait(class, service); + + if (attr_print(ap->fp, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, command, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_STR, MAIL_ATTR_VERPDL, verp, + ATTR_TYPE_END) == 0 + && vstream_fflush(ap->fp) == 0) { + event_enable_read(vstream_fileno(ap->fp), abounce_event, (char *) ap); + } else { + abounce_done(ap, -1); + } +} + +/* abounce_flush_verp - asynchronous bounce flush */ + +void abounce_flush_verp(int flags, const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, char *context) +{ + abounce_request_verp(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE, + BOUNCE_CMD_VERP, flags, queue, id, sender, verp, + callback, context); +} + +/* adefer_flush_verp - asynchronous defer flush */ + +void adefer_flush_verp(int flags, const char *queue, const char *id, + const char *sender, const char *verp, + ABOUNCE_FN callback, char *context) +{ + abounce_request_verp(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, + BOUNCE_CMD_VERP, flags, queue, id, sender, verp, + callback, context); } /* abounce_request - suspend pseudo thread until server reply event */ @@ -171,8 +259,13 @@ static void abounce_request(const char *class, const char *service, ap->context = context; ap->fp = mail_connect_wait(class, service); - if (mail_print(ap->fp, "%d %d %s %s %s %s", command, - flags, queue, id, sender, MAIL_EOF) == 0 + if (attr_print(ap->fp, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, command, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_END) == 0 && vstream_fflush(ap->fp) == 0) { event_enable_read(vstream_fileno(ap->fp), abounce_event, (char *) ap); } else { diff --git a/gnu/dist/postfix/src/global/abounce.h b/gnu/dist/postfix/src/global/abounce.h index 8932248751c..69542383a5b 100644 --- a/gnu/dist/postfix/src/global/abounce.h +++ b/gnu/dist/postfix/src/global/abounce.h @@ -25,6 +25,9 @@ extern void abounce_flush(int, const char *, const char *, const char *, ABOUNCE extern void adefer_flush(int, const char *, const char *, const char *, ABOUNCE_FN, char *); extern void adefer_warn(int, const char *, const char *, const char *, ABOUNCE_FN, char *); +extern void abounce_flush_verp(int, const char *, const char *, const char *, const char *, ABOUNCE_FN, char *); +extern void adefer_flush_verp(int, const char *, const char *, const char *, const char *, ABOUNCE_FN, char *); + /* LICENSE /* .ad /* .fi diff --git a/gnu/dist/postfix/src/global/bounce.c b/gnu/dist/postfix/src/global/bounce.c index 75b07298d87..805d4c78710 100644 --- a/gnu/dist/postfix/src/global/bounce.c +++ b/gnu/dist/postfix/src/global/bounce.c @@ -142,10 +142,14 @@ int vbounce_append(int flags, const char *id, const char *recipient, why = vstring_alloc(100); delay = time((time_t *) 0) - entry; vstring_vsprintf(why, fmt, ap); - if (mail_command_write(MAIL_CLASS_PRIVATE, var_soft_bounce ? - MAIL_SERVICE_DEFER : MAIL_SERVICE_BOUNCE, - "%d %d %s %s %s", BOUNCE_CMD_APPEND, - flags, id, recipient, vstring_str(why)) == 0) { + if (mail_command_client(MAIL_CLASS_PRIVATE, var_soft_bounce ? + MAIL_SERVICE_DEFER : MAIL_SERVICE_BOUNCE, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient, + ATTR_TYPE_STR, MAIL_ATTR_WHY, vstring_str(why), + ATTR_TYPE_END) == 0) { msg_info("%s: to=<%s>, relay=%s, delay=%d, status=%s (%s)", id, recipient, relay, delay, var_soft_bounce ? "deferred" : "bounced", vstring_str(why)); @@ -172,9 +176,13 @@ int bounce_flush(int flags, const char *queue, const char *id, */ if (var_soft_bounce) return (-1); - if (mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE, - "%d %d %s %s %s", BOUNCE_CMD_FLUSH, - flags, queue, id, sender) == 0) { + if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_BOUNCE, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_END) == 0) { return (0); } else if ((flags & BOUNCE_FLAG_CLEAN) == 0) { msg_info("%s: status=deferred (bounce failed)", id); diff --git a/gnu/dist/postfix/src/global/bounce.h b/gnu/dist/postfix/src/global/bounce.h index 43ebc6040bb..018b91a4fe6 100644 --- a/gnu/dist/postfix/src/global/bounce.h +++ b/gnu/dist/postfix/src/global/bounce.h @@ -40,8 +40,8 @@ extern int vbounce_recip(int, const char *, const char *, const char *, */ #define BOUNCE_CMD_APPEND 0 /* append log */ #define BOUNCE_CMD_FLUSH 1 /* send log */ -#define BOUNCE_CMD_WARN 2 /* send warning bounce, don't delete - * log */ +#define BOUNCE_CMD_WARN 2 /* send warning, don't delete log */ +#define BOUNCE_CMD_VERP 3 /* send log, verp style */ /* * Flags. diff --git a/gnu/dist/postfix/src/global/bounce_log.c b/gnu/dist/postfix/src/global/bounce_log.c index 566ad48783b..f0f84153207 100644 --- a/gnu/dist/postfix/src/global/bounce_log.c +++ b/gnu/dist/postfix/src/global/bounce_log.c @@ -24,6 +24,9 @@ /* BOUNCE_LOG *bounce_log_read(bp) /* BOUNCE_LOG *bp; /* +/* BOUNCE_LOG *bounce_log_delrcpt(bp) +/* BOUNCE_LOG *bp; +/* /* void bounce_log_rewind(bp) /* BOUNCE_LOG *bp; /* @@ -31,8 +34,7 @@ /* BOUNCE_LOG *bp; /* DESCRIPTION /* This module implements a bounce/defer logfile API. Information -/* is sanitized for control and non-ASCII characters. Currently, -/* only the reading end is implemented. +/* is sanitized for control and non-ASCII characters. /* /* bounce_log_open() opens the named bounce or defer logfile /* and returns a handle that must be used for further access. @@ -47,6 +49,9 @@ /* bounce_log_read() returns a null pointer when no recipient was read, /* otherwise it returns its argument. /* +/* bounce_log_delrcpt() marks the last accessed recipient record as +/* "deleted". This requires that the logfile is opened for update. +/* /* bounce_log_rewind() is a helper that seeks to the first recipient /* in an open bounce or defer logfile (skipping over recipients that /* are marked as done). The result is 0 in case of success, -1 in case @@ -92,6 +97,7 @@ #include <sys_defs.h> #include <string.h> #include <ctype.h> +#include <unistd.h> /* Utility library. */ @@ -133,6 +139,7 @@ BOUNCE_LOG *bounce_log_open(const char *queue_name, const char *queue_id, bp->fp = fp; bp->buf = vstring_alloc(100); bp->status = STREQ(queue_name, MAIL_QUEUE_DEFER) ? "4.0.0" : "5.0.0"; + bp->offset = 0; return (bp); } } @@ -145,7 +152,8 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) char *text; char *cp; - while (vstring_get_nonl(bp->buf, bp->fp) != VSTREAM_EOF) { + while ((bp->offset = vstream_ftell(bp->fp)), + (vstring_get_nonl(bp->buf, bp->fp) != VSTREAM_EOF)) { if (STR(bp->buf)[0] == 0) continue; @@ -156,6 +164,12 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) cp = printable(STR(bp->buf), '?'); /* + * Skip over deleted recipients. + */ + if (*cp == BOUNCE_LOG_STAT_DELETED) + continue; + + /* * Find the recipient address. */ if (*cp != '<') { @@ -185,6 +199,21 @@ BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *bp) return (0); } +/* bounce_log_delrcpt - mark recipient record as deleted */ + +BOUNCE_LOG *bounce_log_delrcpt(BOUNCE_LOG *bp) +{ + long current_offset; + + current_offset = vstream_ftell(bp->fp); + if (vstream_fseek(bp->fp, bp->offset, SEEK_SET) < 0) + msg_fatal("bounce logfile %s seek error: %m", VSTREAM_PATH(bp->fp)); + VSTREAM_PUTC(BOUNCE_LOG_STAT_DELETED, bp->fp); + if (vstream_fseek(bp->fp, current_offset, SEEK_SET) < 0) + msg_fatal("bounce logfile %s seek error: %m", VSTREAM_PATH(bp->fp)); + return (bp); +} + /* bounce_log_close - close bounce reader stream */ int bounce_log_close(BOUNCE_LOG *bp) diff --git a/gnu/dist/postfix/src/global/bounce_log.h b/gnu/dist/postfix/src/global/bounce_log.h index 7b949e95175..0f91c6f8d68 100644 --- a/gnu/dist/postfix/src/global/bounce_log.h +++ b/gnu/dist/postfix/src/global/bounce_log.h @@ -28,14 +28,18 @@ typedef struct { const char *recipient; /* final recipient */ const char *status; /* recipient status */ const char *text; /* why undeliverable */ + long offset; /* start of current record */ } BOUNCE_LOG; extern BOUNCE_LOG *bounce_log_open(const char *, const char *, int, int); extern BOUNCE_LOG *bounce_log_read(BOUNCE_LOG *); +extern BOUNCE_LOG *bounce_log_delrcpt(BOUNCE_LOG *); extern int bounce_log_close(BOUNCE_LOG *); #define bounce_log_rewind(bp) vstream_fseek((bp)->fp, 0L, SEEK_SET) +#define BOUNCE_LOG_STAT_DELETED 'D' /* deleted record */ + /* LICENSE /* .ad /* .fi diff --git a/gnu/dist/postfix/src/global/cleanup_user.h b/gnu/dist/postfix/src/global/cleanup_user.h index 8dff9102277..c60565d6229 100644 --- a/gnu/dist/postfix/src/global/cleanup_user.h +++ b/gnu/dist/postfix/src/global/cleanup_user.h @@ -34,7 +34,23 @@ #define CLEANUP_STAT_HOVFL (1<<7) /* Header overflow */ #define CLEANUP_STAT_ROVFL (1<<8) /* Recipient overflow */ -#define CLEANUP_STAT_LETHAL ~(CLEANUP_STAT_HOVFL|CLEANUP_STAT_ROVFL) + /* + * These are set when we can't bounce even if we were asked to. + */ +#define CLEANUP_STAT_MASK_CANT_BOUNCE \ + (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE) + + /* + * These are set when we can't examine every record of a message. + */ +#define CLEANUP_STAT_MASK_INCOMPLETE \ + (CLEANUP_STAT_BAD | CLEANUP_STAT_WRITE | CLEANUP_STAT_SIZE) + + /* + * These are relevant for extracting recipients from headers. + */ +#define CLEANUP_STAT_MASK_EXTRACT_RCPT \ + (CLEANUP_STAT_HOVFL | CLEANUP_STAT_ROVFL | CLEANUP_STAT_RCPT) extern const char *cleanup_strerror(unsigned); diff --git a/gnu/dist/postfix/src/global/debug_peer.c b/gnu/dist/postfix/src/global/debug_peer.c index 29170bad58f..c7096c97321 100644 --- a/gnu/dist/postfix/src/global/debug_peer.c +++ b/gnu/dist/postfix/src/global/debug_peer.c @@ -69,6 +69,7 @@ #include <mail_params.h> #include <namadr_list.h> #include <debug_peer.h> +#include <match_parent_style.h> /* Application-specific. */ @@ -97,7 +98,9 @@ void debug_peer_init(void) * Finally. */ if (*var_debug_peer_list) - debug_peer_list = namadr_list_init(var_debug_peer_list); + debug_peer_list = + namadr_list_init(match_parent_style(VAR_DEBUG_PEER_LIST), + var_debug_peer_list); } /* debug_peer_check - see if this peer needs verbose logging */ diff --git a/gnu/dist/postfix/src/global/defer.c b/gnu/dist/postfix/src/global/defer.c index 59cde38fa41..40130cfe37d 100644 --- a/gnu/dist/postfix/src/global/defer.c +++ b/gnu/dist/postfix/src/global/defer.c @@ -145,9 +145,13 @@ int vdefer_append(int flags, const char *id, const char *recipient, const char *rcpt_domain; vstring_vsprintf(why, fmt, ap); - if (mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, - "%d %d %s %s %s", BOUNCE_CMD_APPEND, - flags, id, recipient, vstring_str(why)) != 0) + if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_APPEND, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient, + ATTR_TYPE_STR, MAIL_ATTR_WHY, vstring_str(why), + ATTR_TYPE_END) != 0) msg_warn("%s: defer service failure", id); msg_info("%s: to=<%s>, relay=%s, delay=%d, status=deferred (%s)", id, recipient, relay, delay, vstring_str(why)); @@ -158,8 +162,14 @@ int vdefer_append(int flags, const char *id, const char *recipient, * bounce/defer daemon? Well, doing it here is more robust. */ if ((rcpt_domain = strrchr(recipient, '@')) != 0 && *++rcpt_domain != 0) - if (flush_add(rcpt_domain, id) != FLUSH_STAT_OK) + switch (flush_add(rcpt_domain, id)) { + case FLUSH_STAT_OK: + case FLUSH_STAT_DENY: + break; + default: msg_warn("unable to talk to fast flush service"); + break; + } return (-1); } @@ -169,9 +179,13 @@ int vdefer_append(int flags, const char *id, const char *recipient, int defer_flush(int flags, const char *queue, const char *id, const char *sender) { - if (mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, - "%d %d %s %s %s", BOUNCE_CMD_FLUSH, - flags, queue, id, sender) == 0) { + if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_FLUSH, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_END) == 0) { return (0); } else { return (-1); @@ -184,9 +198,13 @@ int defer_flush(int flags, const char *queue, const char *id, int defer_warn(int flags, const char *queue, const char *id, const char *sender) { - if (mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, - "%d %d %s %s %s", BOUNCE_CMD_WARN, - flags, queue, id, sender) == 0) { + if (mail_command_client(MAIL_CLASS_PRIVATE, MAIL_SERVICE_DEFER, + ATTR_TYPE_NUM, MAIL_ATTR_NREQ, BOUNCE_CMD_WARN, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, queue, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_END) == 0) { return (0); } else { return (-1); diff --git a/gnu/dist/postfix/src/global/deliver_pass.c b/gnu/dist/postfix/src/global/deliver_pass.c index 2deec8c3c2a..f428d551ebf 100644 --- a/gnu/dist/postfix/src/global/deliver_pass.c +++ b/gnu/dist/postfix/src/global/deliver_pass.c @@ -77,7 +77,9 @@ static int deliver_pass_initial_reply(VSTREAM *stream) { int stat; - if (mail_scan(stream, "%d", &stat) != 1) { + if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 1) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); stat = -1; } @@ -91,14 +93,21 @@ static int deliver_pass_send_request(VSTREAM *stream, DELIVER_REQUEST *request, { int stat; - mail_print(stream, "%d %s %s %ld %ld %s %s %s %s %ld %ld %s %s", - request->flags, - request->queue_name, request->queue_id, - request->data_offset, request->data_size, - nexthop, request->sender, - request->errors_to, request->return_receipt, - request->arrival_time, - offs, addr, "0"); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, request->flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, request->queue_name, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, request->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_SENDER, request->sender, + ATTR_TYPE_STR, MAIL_ATTR_ERRTO, request->errors_to, + ATTR_TYPE_STR, MAIL_ATTR_RRCPT, request->return_receipt, + ATTR_TYPE_LONG, MAIL_ATTR_TIME, request->arrival_time, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, offs, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, addr, + ATTR_TYPE_NUM, MAIL_ATTR_OFFSET, 0, + ATTR_TYPE_END); if (vstream_fflush(stream)) { msg_warn("%s: bad write: %m", VSTREAM_PATH(stream)); @@ -115,7 +124,10 @@ static int deliver_pass_final_reply(VSTREAM *stream, VSTRING *reason) { int stat; - if (mail_scan(stream, "%s %d", reason, &stat) != 2) { + if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_WHY, reason, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 2) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); stat = -1; } diff --git a/gnu/dist/postfix/src/global/deliver_request.c b/gnu/dist/postfix/src/global/deliver_request.c index 63c20c4fee4..35c83e78061 100644 --- a/gnu/dist/postfix/src/global/deliver_request.c +++ b/gnu/dist/postfix/src/global/deliver_request.c @@ -67,7 +67,7 @@ /* Warnings: bad data sent by the client. Fatal errors: out of /* memory, queue file open errors. /* SEE ALSO -/* mail_scan(3) low-level intra-mail input routines +/* attr_scan(3) low-level intra-mail input routines /* LICENSE /* .ad /* .fi @@ -85,6 +85,7 @@ #include <sys/stat.h> #include <string.h> #include <unistd.h> +#include <errno.h> /* Utility library. */ @@ -117,7 +118,9 @@ static int deliver_request_initial(VSTREAM *stream) */ if (msg_verbose) msg_info("deliver_request_initial: send initial status"); - mail_print(stream, "%d", 0); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, 0, + ATTR_TYPE_END); if ((err = vstream_fflush(stream)) != 0) if (msg_verbose) msg_warn("send initial status: %m"); @@ -137,7 +140,10 @@ static int deliver_request_final(VSTREAM *stream, char *reason, int status) reason = ""; if (msg_verbose) msg_info("deliver_request_final: send: \"%s\" %d", reason, status); - mail_print(stream, "%s %d", reason, status); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_WHY, reason, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_END); if ((err = vstream_fflush(stream)) != 0) if (msg_verbose) msg_warn("send final status: %m"); @@ -186,11 +192,18 @@ static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request) * Extract the queue file name, data offset, and sender address. Abort * the conversation when they send bad information. */ - if (mail_scan(stream, "%d %s %s %ld %ld %s %s %s %s %ld", - &request->flags, - queue_name, queue_id, &request->data_offset, - &request->data_size, nexthop, address, - errors_to, return_receipt, &request->arrival_time) != 10) + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + ATTR_TYPE_NUM, 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_SENDER, address, + ATTR_TYPE_STR, MAIL_ATTR_ERRTO, errors_to, + ATTR_TYPE_STR, MAIL_ATTR_RRCPT, return_receipt, + ATTR_TYPE_LONG, MAIL_ATTR_TIME, &request->arrival_time, + ATTR_TYPE_END) != 10) return (-1); if (mail_open_ok(vstring_str(queue_name), vstring_str(queue_id), &st, &path) == 0) @@ -204,14 +217,19 @@ static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request) request->return_receipt = mystrdup(vstring_str(return_receipt)); /* - * Extract the recipient offset and address list. + * Extract the recipient offset and address list. Skip over any + * attributes from the sender that we do not understand. */ for (;;) { - if (mail_scan(stream, "%ld", &offset) != 1) + if (attr_scan(stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, &offset, + ATTR_TYPE_END) != 1) return (-1); if (offset == 0) break; - if (mail_scan(stream, "%s", address) != 1) + if (attr_scan(stream, ATTR_FLAG_MORE | ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, address, + ATTR_TYPE_END) != 1) return (-1); recipient_list_add(&request->rcpt_list, offset, vstring_str(address)); } @@ -230,8 +248,12 @@ static int deliver_request_get(VSTREAM *stream, DELIVER_REQUEST *request) request->fp = mail_queue_open(request->queue_name, request->queue_id, O_RDWR, 0); - if (request->fp == 0) - msg_fatal("open %s %s: %m", request->queue_name, request->queue_id); + 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) @@ -309,9 +331,11 @@ DELIVER_REQUEST *deliver_request_read(VSTREAM *stream) /* * Allocate and read the queue manager's delivery request. */ +#define XXX_DEFER_STATUS -1 + request = deliver_request_alloc(); if (deliver_request_get(stream, request) < 0) { - deliver_request_free(request); + deliver_request_done(stream, request, XXX_DEFER_STATUS); request = 0; } return (request); diff --git a/gnu/dist/postfix/src/global/deliver_request.h b/gnu/dist/postfix/src/global/deliver_request.h index 2406085ff1e..45c2055234c 100644 --- a/gnu/dist/postfix/src/global/deliver_request.h +++ b/gnu/dist/postfix/src/global/deliver_request.h @@ -45,6 +45,17 @@ typedef struct DELIVER_REQUEST { #define DEL_REQ_FLAG_SUCCESS (1<<0) /* delete successful recipients */ #define DEL_REQ_FLAG_BOUNCE (1<<1) /* unimplemented */ + /* + * Delivery status. Note that there are only FINAL and DEFER. This is + * because delivery status information can be lost when a delivery agent or + * queue manager process terminates prematurely. The only distinctions we + * can rely on are "final delivery completed" and "everything else". In the + * absence of a definitive statement the queue manager will always have to + * be prepared for all possibilities. + */ +#define DEL_STAT_FINAL 0 /* delivered or bounced */ +#define DEL_STAT_DEFER (-1) /* not delivered or bounced */ + typedef struct VSTREAM _deliver_vstream_; extern DELIVER_REQUEST *deliver_request_read(_deliver_vstream_ *); extern int deliver_request_done(_deliver_vstream_ *, DELIVER_REQUEST *, int); diff --git a/gnu/dist/postfix/src/global/domain_list.c b/gnu/dist/postfix/src/global/domain_list.c index 1322642d0f9..d4ea536b29f 100644 --- a/gnu/dist/postfix/src/global/domain_list.c +++ b/gnu/dist/postfix/src/global/domain_list.c @@ -6,7 +6,8 @@ /* SYNOPSIS /* #include <domain_list.h> /* -/* DOMAIN_LIST *domain_list_init(pattern_list) +/* DOMAIN_LIST *domain_list_init(flags, pattern_list) +/* int flags; /* const char *pattern_list; /* /* int domain_list_match(list, name) @@ -16,6 +17,8 @@ /* void domain_list_free(list) /* DOMAIN_LIST *list; /* DESCRIPTION +/* This is a convenience wrapper around the match_list module. +/* /* This module implements tests for list membership of a host or /* domain name. /* @@ -30,9 +33,17 @@ /* insensitive. In order to reverse the result, precede a non-file /* name pattern with an exclamation point (!). /* -/* domain_list_init() performs initializations. The argument is a -/* list of domain patterns, or the name of a file containing domain -/* patterns. +/* domain_list_init() performs initializations. The first argument +/* is the bit-wise OR of zero or more of the following: +/* .RS +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches itself and any name below +/* the domain foo.com. If this flag is cleared, foo.com matches itself +/* only, and .foo.com matches any name below the domain foo.com. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* The second argument is a list of domain patterns, or the name of +/* a file containing domain patterns. /* /* domain_list_match() matches the specified host or domain name /* against the specified pattern list. @@ -62,33 +73,11 @@ /* Utility library. */ #include <match_list.h> -#include <match_ops.h> /* Global library. */ #include "domain_list.h" -/* domain_list_init - initialize domain list */ - -DOMAIN_LIST *domain_list_init(const char *patterns) -{ - return (match_list_init(patterns, 1, match_hostname)); -} - -/* domain_list_match - match host against domain list */ - -int domain_list_match(DOMAIN_LIST *list, const char *name) -{ - return (match_list_match(list, name)); -} - -/* domain_list_free - release storage */ - -void domain_list_free(DOMAIN_LIST *list) -{ - match_list_free(list); -} - #ifdef TEST #include <msg.h> @@ -120,7 +109,7 @@ main(int argc, char **argv) } if (argc != optind + 2) usage(argv[0]); - list = domain_list_init(argv[optind]); + list = domain_list_init(MATCH_FLAG_PARENT, argv[optind]); host = argv[optind + 1]; vstream_printf("%s: %s\n", host, domain_list_match(list, host) ? "YES" : "NO"); diff --git a/gnu/dist/postfix/src/global/domain_list.h b/gnu/dist/postfix/src/global/domain_list.h index b36f516b444..23f60c78950 100644 --- a/gnu/dist/postfix/src/global/domain_list.h +++ b/gnu/dist/postfix/src/global/domain_list.h @@ -12,13 +12,19 @@ /* .nf /* + * Utility library. + */ +#include <match_list.h> +#include <match_ops.h> + + /* * External interface. */ -typedef struct MATCH_LIST DOMAIN_LIST; +#define DOMAIN_LIST MATCH_LIST -extern DOMAIN_LIST *domain_list_init(const char *); -extern int domain_list_match(DOMAIN_LIST *, const char *); -extern void domain_list_free(DOMAIN_LIST *); +#define domain_list_init(f, p) match_list_init((f), (p), 1, match_hostname) +#define domain_list_match match_list_match +#define domain_list_free match_list_free /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/global/file_id.c b/gnu/dist/postfix/src/global/file_id.c index ad4ce57987f..e130087db24 100644 --- a/gnu/dist/postfix/src/global/file_id.c +++ b/gnu/dist/postfix/src/global/file_id.c @@ -65,7 +65,7 @@ const char *get_file_id(int fd) result = vstring_alloc(1); if (fstat(fd, &st) < 0) msg_fatal("fstat: %m"); - vstring_sprintf(result, "%X", (int) st.st_ino); + vstring_sprintf(result, "%lX", (long) st.st_ino); return (vstring_str(result)); } diff --git a/gnu/dist/postfix/src/global/flush_clnt.c b/gnu/dist/postfix/src/global/flush_clnt.c index 6de4b426de8..8f35b6c5bbf 100644 --- a/gnu/dist/postfix/src/global/flush_clnt.c +++ b/gnu/dist/postfix/src/global/flush_clnt.c @@ -46,6 +46,9 @@ /* .IP MAIL_FLUSH_BAD /* The "fast flush" server rejected the request (invalid request /* parameter). +/* .IP MAIL_FLUSH_DENY +/* The specified domain is not eligible for "fast flush" service, +/* or the "fast flush" service is disabled. /* SEE ALSO /* flush(8) Postfix fast flush cache manager /* LICENSE @@ -95,10 +98,11 @@ int flush_purge(void) * Don't bother the server if the service is turned off. */ if (*var_fflush_domains == 0) - status = FLUSH_STAT_OK; + status = FLUSH_STAT_DENY; else - status = mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_FLUSH, - "%s", FLUSH_REQ_PURGE); + status = mail_command_client(MAIL_CLASS_PUBLIC, MAIL_SERVICE_FLUSH, + ATTR_TYPE_STR, MAIL_ATTR_REQ, FLUSH_REQ_PURGE, + ATTR_TYPE_END); if (msg_verbose) msg_info("%s: status %d", myname, status); @@ -120,10 +124,11 @@ int flush_refresh(void) * Don't bother the server if the service is turned off. */ if (*var_fflush_domains == 0) - status = FLUSH_STAT_OK; + status = FLUSH_STAT_DENY; else - status = mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_FLUSH, - "%s", FLUSH_REQ_REFRESH); + status = mail_command_client(MAIL_CLASS_PUBLIC, MAIL_SERVICE_FLUSH, + ATTR_TYPE_STR, MAIL_ATTR_REQ, FLUSH_REQ_REFRESH, + ATTR_TYPE_END); if (msg_verbose) msg_info("%s: status %d", myname, status); @@ -145,10 +150,12 @@ int flush_send(const char *site) * Don't bother the server if the service is turned off. */ if (*var_fflush_domains == 0) - status = mail_flush_deferred(); + status = FLUSH_STAT_DENY; else - status = mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_FLUSH, - "%s %s", FLUSH_REQ_SEND, site); + status = mail_command_client(MAIL_CLASS_PUBLIC, MAIL_SERVICE_FLUSH, + ATTR_TYPE_STR, MAIL_ATTR_REQ, FLUSH_REQ_SEND, + ATTR_TYPE_STR, MAIL_ATTR_SITE, site, + ATTR_TYPE_END); if (msg_verbose) msg_info("%s: site %s status %d", myname, site, status); @@ -170,10 +177,13 @@ int flush_add(const char *site, const char *queue_id) * Don't bother the server if the service is turned off. */ if (*var_fflush_domains == 0) - status = FLUSH_STAT_OK; + status = FLUSH_STAT_DENY; else - status = mail_command_write(MAIL_CLASS_PRIVATE, MAIL_SERVICE_FLUSH, - "%s %s %s", FLUSH_REQ_ADD, site, queue_id); + status = mail_command_client(MAIL_CLASS_PUBLIC, MAIL_SERVICE_FLUSH, + ATTR_TYPE_STR, MAIL_ATTR_REQ, FLUSH_REQ_ADD, + ATTR_TYPE_STR, MAIL_ATTR_SITE, site, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, queue_id, + ATTR_TYPE_END); if (msg_verbose) msg_info("%s: site %s id %s status %d", myname, site, queue_id, diff --git a/gnu/dist/postfix/src/global/flush_clnt.h b/gnu/dist/postfix/src/global/flush_clnt.h index 47a88558a53..ee82a984cb4 100644 --- a/gnu/dist/postfix/src/global/flush_clnt.h +++ b/gnu/dist/postfix/src/global/flush_clnt.h @@ -33,6 +33,7 @@ extern int flush_purge(void); #define FLUSH_STAT_FAIL -1 /* request failed */ #define FLUSH_STAT_OK 0 /* request executed */ #define FLUSH_STAT_BAD 3 /* invalid parameter */ +#define FLUSH_STAT_DENY 4 /* request denied */ /* LICENSE diff --git a/gnu/dist/postfix/src/global/is_header.c b/gnu/dist/postfix/src/global/is_header.c index 0cd3e93c2d5..5ed88fadd6e 100644 --- a/gnu/dist/postfix/src/global/is_header.c +++ b/gnu/dist/postfix/src/global/is_header.c @@ -41,6 +41,13 @@ int is_header(const char *str) const char *cp; int c; + /* + * XXX RFC 2822 Section 4.5.2, Obsolete header fields: whitespace may + * appear between header label and ":" (see: RFC 822, Section 3.4.2.). + * + * The code below allows no such whitespace. This has never been a problem, + * and therefore we're not inclined to add code for it. + */ for (cp = str; (c = *(unsigned char *) cp) != 0; cp++) { if (c == ':') return (cp > str); diff --git a/gnu/dist/postfix/src/global/mail_command_client.c b/gnu/dist/postfix/src/global/mail_command_client.c new file mode 100644 index 00000000000..fc1c56c9efe --- /dev/null +++ b/gnu/dist/postfix/src/global/mail_command_client.c @@ -0,0 +1,82 @@ +/*++ +/* NAME +/* mail_command_client 3 +/* SUMMARY +/* single-command client +/* SYNOPSIS +/* #include <mail_proto.h> +/* +/* int mail_command_client(class, name, type, attr, ...) +/* const char *class; +/* const char *name; +/* int type; +/* const char *attr; +/* DESCRIPTION +/* This module implements a client interface for single-command +/* clients: a client that sends a single command and expects +/* a single completion status code. +/* +/* Arguments: +/* .IP class +/* Service type: MAIL_CLASS_PUBLIC or MAIL_CLASS_PRIVATE +/* .IP name +/* Service name (master.cf). +/* .IP "type, attr, ..." +/* Attribute information as defined in attr_print(3). +/* DIAGNOSTICS +/* The result is -1 if the request could not be sent, otherwise +/* the result is the status reported by the server. +/* Warnings: problems connecting to the requested service. +/* Fatal: out of memory. +/* SEE ALSO +/* attr_print(3), send attributes over byte stream +/* mail_command_server(3), server interface +/* mail_proto(3h), client-server protocol +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> + +/* Utility library. */ + +#include <vstream.h> + +/* Global library. */ + +#include <mail_proto.h> + +/* mail_command_client - single-command transaction with completion status */ + +int mail_command_client(const char *class, const char *name,...) +{ + va_list ap; + VSTREAM *stream; + int status; + + /* + * Talk a little protocol with the specified service. + */ + if ((stream = mail_connect(class, name, BLOCKING)) == 0) + return (-1); + va_start(ap, name); + status = attr_vprint(stream, ATTR_FLAG_NONE, ap); + va_end(ap); + if (status != 0 + || attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, 0) != 1) + status = -1; + (void) vstream_fclose(stream); + return (status); +} diff --git a/gnu/dist/postfix/src/global/mail_command_server.c b/gnu/dist/postfix/src/global/mail_command_server.c new file mode 100644 index 00000000000..9565b55fb4c --- /dev/null +++ b/gnu/dist/postfix/src/global/mail_command_server.c @@ -0,0 +1,67 @@ +/*++ +/* NAME +/* mail_command_server 3 +/* SUMMARY +/* single-command server +/* SYNOPSIS +/* #include <mail_proto.h> +/* +/* int mail_command_server(stream, type, name, ...) +/* VSTREAM *stream; +/* int type; +/* const char *name; +/* DESCRIPTION +/* This module implements the server interface for single-command +/* requests: a clients sends a single command and expects a single +/* completion status code. +/* +/* Arguments: +/* .IP stream +/* Server endpoint. +/* .IP "type, name, ..." +/* Attribute list as defined in attr_scan(3). +/* DIAGNOSTICS +/* Fatal: out of memory. +/* SEE ALSO +/* attr_scan(3) +/* mail_command_client(3) client interface +/* mail_proto(3h), client-server protocol +#include <mail_proto.h> +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <vstream.h> + +/* Global library. */ + +#include "mail_proto.h" + +/* mail_command_server - read single-command request */ + +int mail_command_server(VSTREAM *stream,...) +{ + va_list ap; + int count; + + va_start(ap, stream); + count = attr_vscan(stream, ATTR_FLAG_MISSING, ap); + va_end(ap); + return (count); +} diff --git a/gnu/dist/postfix/src/global/mail_conf.c b/gnu/dist/postfix/src/global/mail_conf.c index 2af13f29c55..df25bed6974 100644 --- a/gnu/dist/postfix/src/global/mail_conf.c +++ b/gnu/dist/postfix/src/global/mail_conf.c @@ -73,7 +73,9 @@ /* System library. */ #include <sys_defs.h> +#include <unistd.h> #include <stdlib.h> +#include <string.h> /* Utility library. */ @@ -84,12 +86,55 @@ #include <dict.h> #include <safe.h> #include <stringops.h> +#include <readlline.h> /* Global library. */ #include "mail_params.h" #include "mail_conf.h" +/* mail_conf_checkdir - authorize non-default directory */ + +static void mail_conf_checkdir(const char *config_dir) +{ + VSTRING *buf; + VSTREAM *fp; + char *path; + char *name; + char *value; + char *cp; + int found = 0; + + /* + * If running set-[ug]id, require that a non-default configuration + * directory name is blessed as a bona fide configuration directory in + * the default main.cf file. + */ + path = concatenate(DEF_CONFIG_DIR, "/", "main.cf", (char *) 0); + if ((fp = vstream_fopen(path, O_RDONLY, 0)) == 0) + msg_fatal("open file %s: %m", path); + + buf = vstring_alloc(1); + while (found == 0 && readlline(buf, fp, (int *) 0)) { + if (split_nameval(vstring_str(buf), &name, &value) == 0 + && strcmp(name, VAR_CONFIG_DIRS) == 0) { + while (found == 0 && (cp = mystrtok(&value, ", \t\r\n")) != 0) + if (strcmp(cp, config_dir) == 0) + found = 1; + } + } + if (vstream_fclose(fp)) + msg_fatal("read file %s: %m", path); + vstring_free(buf); + + if (found == 0) { + msg_error("untrusted configuration directory name: %s", config_dir); + msg_fatal("specify \"%s = %s\" in %s", + VAR_CONFIG_DIRS, config_dir, path); + } + myfree(path); +} + /* mail_conf_read - read global configuration file */ void mail_conf_read(void) @@ -114,9 +159,19 @@ void mail_conf_suck(void) dict_unknown_allowed = 1; if (var_config_dir) myfree(var_config_dir); - var_config_dir = mystrdup((config_dir = safe_getenv(CONF_ENV_PATH)) != 0 ? - config_dir : DEF_CONFIG_DIR); /* XXX */ + if ((config_dir = getenv(CONF_ENV_PATH)) == 0) + config_dir = DEF_CONFIG_DIR; + var_config_dir = mystrdup(config_dir); set_mail_conf_str(VAR_CONFIG_DIR, var_config_dir); + + /* + * If the configuration directory name comes from a different trust + * domain, require that it is listed in the default main.cf file. + */ + if (strcmp(var_config_dir, DEF_CONFIG_DIR) != 0 /* non-default */ + && safe_getenv(CONF_ENV_PATH) == 0 /* non-default */ + && geteuid() != 0) /* untrusted */ + mail_conf_checkdir(var_config_dir); path = concatenate(var_config_dir, "/", "main.cf", (char *) 0); dict_load_file(CONFIG_DICT, path); myfree(path); diff --git a/gnu/dist/postfix/src/global/mail_conf.h b/gnu/dist/postfix/src/global/mail_conf.h index ffa1341d7ca..25406e8da14 100644 --- a/gnu/dist/postfix/src/global/mail_conf.h +++ b/gnu/dist/postfix/src/global/mail_conf.h @@ -21,7 +21,8 @@ */ #define CONF_ENV_PATH "MAIL_CONFIG" /* config database */ #define CONF_ENV_VERB "MAIL_VERBOSE" /* verbose mode on */ -#define CONF_ENV_DEBUG "MAIL_DEBUG" /* verbose mode on */ +#define CONF_ENV_DEBUG "MAIL_DEBUG" /* live debugging */ +#define CONF_ENV_LOGTAG "MAIL_LOGTAG" /* instance name */ /* * External representation for booleans. diff --git a/gnu/dist/postfix/src/global/mail_connect.c b/gnu/dist/postfix/src/global/mail_connect.c index cd486f9bc40..a8ef96d1364 100644 --- a/gnu/dist/postfix/src/global/mail_connect.c +++ b/gnu/dist/postfix/src/global/mail_connect.c @@ -66,6 +66,7 @@ #include <connect.h> #include <mymalloc.h> #include <iostuff.h> +#include <stringops.h> /* Global library. */ @@ -79,6 +80,7 @@ VSTREAM *mail_connect(const char *class, const char *name, int block_mode) char *path; VSTREAM *stream; int fd; + char *sock_name; path = mail_pathname(class, name); if ((fd = LOCAL_CONNECT(path, block_mode, 0)) < 0) { @@ -90,9 +92,11 @@ VSTREAM *mail_connect(const char *class, const char *name, int block_mode) msg_info("connect to subsystem %s", path); stream = vstream_fdopen(fd, O_RDWR); timed_ipc_setup(stream); + sock_name = concatenate(path, " socket", (char *) 0); vstream_control(stream, - VSTREAM_CTL_PATH, path, + VSTREAM_CTL_PATH, sock_name, VSTREAM_CTL_END); + myfree(sock_name); } myfree(path); return (stream); diff --git a/gnu/dist/postfix/src/global/mail_copy.c b/gnu/dist/postfix/src/global/mail_copy.c index e08d301f56e..c2d328981ca 100644 --- a/gnu/dist/postfix/src/global/mail_copy.c +++ b/gnu/dist/postfix/src/global/mail_copy.c @@ -62,6 +62,14 @@ /* DIAGNOSTICS /* A non-zero result means the operation failed. Warnings: corrupt /* message file. A corrupt message is marked as corrupt. +/* +/* The result is the bit-wise OR of zero or more of the following: +/* .IP MAIL_COPY_STAT_CORRUPT +/* The queue file is marked as corrupt. +/* .IP MAIL_COPY_STAT_READ +/* A read error was detected; errno specifies the nature of the problem. +/* .IP MAIL_COPY_STAT_WRITE +/* A write error was detected; errno specifies the nature of the problem. /* SEE ALSO /* mark_corrupt(3), mark queue file as corrupted. /* LICENSE @@ -81,6 +89,7 @@ #include <string.h> #include <unistd.h> #include <time.h> +#include <errno.h> /* Utility library. */ @@ -98,8 +107,9 @@ #include "rec_type.h" #include "mail_queue.h" #include "mail_addr.h" -#include "mail_copy.h" #include "mark_corrupt.h" +#include "mail_params.h" +#include "mail_copy.h" /* mail_copy - copy message with extreme prejudice */ @@ -183,6 +193,8 @@ int mail_copy(const char *sender, const char *delivered, prev_type = type; } if (vstream_ferror(dst) == 0) { + if (var_fault_inj_code == 1) + type = 0; if (type != REC_TYPE_XTRA) corrupt_error = mark_corrupt(src); if (prev_type != REC_TYPE_NORM) @@ -208,9 +220,17 @@ int mail_copy(const char *sender, const char *delivered, if ((flags & MAIL_COPY_TOFILE) != 0) write_error |= fsync(vstream_fileno(dst)); #endif + if (var_fault_inj_code == 2) { + read_error = 1; + errno = ENOENT; + } + if (var_fault_inj_code == 3) { + write_error = 1; + errno = ENOENT; + } #ifndef NO_TRUNCATE if ((flags & MAIL_COPY_TOFILE) != 0) - if (read_error || write_error) + if (corrupt_error || read_error || write_error) ftruncate(vstream_fileno(dst), (off_t) orig_length); #endif write_error |= vstream_fclose(dst); @@ -218,5 +238,7 @@ int mail_copy(const char *sender, const char *delivered, vstring_sprintf(why, "error reading message: %m"); if (why && write_error) vstring_sprintf(why, "error writing message: %m"); - return (corrupt_error || read_error || write_error); + return ((corrupt_error ? MAIL_COPY_STAT_CORRUPT : 0) + | (read_error ? MAIL_COPY_STAT_READ : 0) + | (write_error ? MAIL_COPY_STAT_WRITE : 0)); } diff --git a/gnu/dist/postfix/src/global/mail_copy.h b/gnu/dist/postfix/src/global/mail_copy.h index 2d8c6ae39f1..efa1957c5c0 100644 --- a/gnu/dist/postfix/src/global/mail_copy.h +++ b/gnu/dist/postfix/src/global/mail_copy.h @@ -35,6 +35,11 @@ extern int mail_copy(const char *, const char *, VSTREAM *, VSTREAM *, MAIL_COPY_RETURN_PATH | MAIL_COPY_BLANK) #define MAIL_COPY_NONE 0 /* all turned off */ +#define MAIL_COPY_STAT_OK 0 +#define MAIL_COPY_STAT_CORRUPT (1<<0) +#define MAIL_COPY_STAT_READ (1<<1) +#define MAIL_COPY_STAT_WRITE (1<<2) + /* LICENSE /* .ad /* .fi diff --git a/gnu/dist/postfix/src/global/mail_params.c b/gnu/dist/postfix/src/global/mail_params.c index ed04630aad6..bf134b11a0e 100644 --- a/gnu/dist/postfix/src/global/mail_params.c +++ b/gnu/dist/postfix/src/global/mail_params.c @@ -14,9 +14,12 @@ /* char *var_transit_origin; /* char *var_transit_dest; /* char *var_mail_name; +/* char *var_syslog_name; /* char *var_mail_owner; /* uid_t var_owner_uid; /* gid_t var_owner_gid; +/* char *var_sgid_group; +/* gid_t var_sgid_gid; /* char *var_default_privs; /* uid_t var_default_uid; /* gid_t var_default_gid; @@ -39,6 +42,7 @@ /* int var_line_limit; /* char *var_alias_db_map; /* int var_message_limit; +/* char *var_mail_release; /* char *var_mail_version; /* int var_ipc_idle_limit; /* char *var_db_type; @@ -61,9 +65,17 @@ /* char *var_fflush_domains; /* char *var_def_transport; /* char *var_mynetworks_style; +/* char *var_verp_delims; +/* char *var_verp_filter; +/* char *var_par_dom_match; +/* char *var_config_dirs; /* /* char *var_import_environ; /* char *var_export_environ; +/* char *var_debug_peer_list; +/* int var_debug_peer_level; +/* int var_in_flow_delay; +/* int var_fault_inj_code; /* /* void mail_params_init() /* DESCRIPTION @@ -92,8 +104,10 @@ #include <sys_defs.h> #include <unistd.h> +#include <stdlib.h> #include <string.h> #include <pwd.h> +#include <grp.h> #include <time.h> #ifdef STRCASECMP_IN_STRINGS_H @@ -107,6 +121,7 @@ #include <get_hostname.h> #include <valid_hostname.h> #include <stringops.h> +#include <safe.h> /* Global library. */ @@ -114,6 +129,7 @@ #include "mail_conf.h" #include "mail_version.h" #include "mail_proto.h" +#include "verp_sender.h" #include "mail_params.h" /* @@ -127,9 +143,12 @@ char *var_relayhost; char *var_transit_origin; char *var_transit_dest; char *var_mail_name; +char *var_syslog_name; char *var_mail_owner; uid_t var_owner_uid; gid_t var_owner_gid; +char *var_sgid_group; +gid_t var_sgid_gid; char *var_default_privs; uid_t var_default_uid; gid_t var_default_gid; @@ -152,6 +171,7 @@ char *var_double_bounce_sender; int var_line_limit; char *var_alias_db_map; int var_message_limit; +char *var_mail_release; char *var_mail_version; int var_ipc_idle_limit; char *var_db_type; @@ -174,9 +194,19 @@ char *var_relay_domains; char *var_fflush_domains; char *var_def_transport; char *var_mynetworks_style; +char *var_verp_delims; +char *var_verp_filter; +int var_in_flow_delay; +char *var_par_dom_match; +char *var_config_dirs; char *var_import_environ; char *var_export_environ; +char *var_debug_peer_list; +int var_debug_peer_level; +int var_fault_inj_code; + +#define MAIN_CONF_FILE "main.cf" /* check_myhostname - lookup hostname and validate */ @@ -199,9 +229,11 @@ static const char *check_myhostname(void) name = get_hostname(); if ((dot = strchr(name, '.')) == 0) { if ((domain = mail_conf_lookup_eval(VAR_MYDOMAIN)) == 0) - msg_fatal("My hostname %s is not a fully qualified name - set %s or %s in %s/main.cf", - name, VAR_MYHOSTNAME, VAR_MYDOMAIN, var_config_dir); - name = concatenate(name, ".", domain, (char *) 0); + msg_warn("My hostname %s is not a fully qualified name - set %s or %s in %s/%s", + name, VAR_MYHOSTNAME, VAR_MYDOMAIN, + var_config_dir, MAIN_CONF_FILE); + else + name = concatenate(name, ".", domain, (char *) 0); } return (name); } @@ -228,13 +260,16 @@ static void check_default_privs(void) struct passwd *pwd; if ((pwd = getpwnam(var_default_privs)) == 0) - msg_fatal("unknown %s configuration parameter value: %s", + msg_fatal("file %s/%s: parameter %s: unknown user name value: %s", + var_config_dir, MAIN_CONF_FILE, VAR_DEFAULT_PRIVS, var_default_privs); if ((var_default_uid = pwd->pw_uid) == 0) - msg_fatal("%s: %s: privileged user is not allowed", + msg_fatal("file %s/%s: parameter %s: user %s has privileged user ID", + var_config_dir, MAIN_CONF_FILE, VAR_DEFAULT_PRIVS, var_default_privs); if ((var_default_gid = pwd->pw_gid) == 0) - msg_fatal("%s: %s: privileged group is not allowed", + msg_fatal("file %s/%s: parameter %s: user %s has privileged group ID", + var_config_dir, MAIN_CONF_FILE, VAR_DEFAULT_PRIVS, var_default_privs); } @@ -245,14 +280,91 @@ static void check_mail_owner(void) struct passwd *pwd; if ((pwd = getpwnam(var_mail_owner)) == 0) - msg_fatal("unknown %s configuration parameter value: %s", + msg_fatal("file %s/%s: parameter %s: unknown user name value: %s", + var_config_dir, MAIN_CONF_FILE, VAR_MAIL_OWNER, var_mail_owner); if ((var_owner_uid = pwd->pw_uid) == 0) - msg_fatal("%s: %s: privileged user is not allowed", + msg_fatal("file %s/%s: parameter %s: user %s has privileged user ID", + var_config_dir, MAIN_CONF_FILE, VAR_MAIL_OWNER, var_mail_owner); if ((var_owner_gid = pwd->pw_gid) == 0) - msg_fatal("%s: %s: privileged group is not allowed", - VAR_DEFAULT_PRIVS, var_mail_owner); + msg_fatal("file %s/%s: parameter %s: user %s has privileged group ID", + var_config_dir, MAIN_CONF_FILE, + VAR_MAIL_OWNER, var_mail_owner); + + /* + * This detects only some forms of sharing. Enumerating the entire + * password file name space could be expensive. The purpose of this code + * is to discourage user ID sharing by developers and package + * maintainers. + */ + if ((pwd = getpwuid(var_owner_uid)) != 0 + && strcmp(pwd->pw_name, var_mail_owner) != 0) + msg_fatal("file %s/%s: parameter %s: user %s has same user ID as %s", + var_config_dir, MAIN_CONF_FILE, + VAR_MAIL_OWNER, var_mail_owner, pwd->pw_name); +} + +/* check_sgid_group - lookup setgid group attributes and validate */ + +static void check_sgid_group(void) +{ + struct group *grp; + + if ((grp = getgrnam(var_sgid_group)) == 0) + msg_fatal("file %s/%s: parameter %s: unknown group name: %s", + var_config_dir, MAIN_CONF_FILE, + VAR_SGID_GROUP, var_sgid_group); + if ((var_sgid_gid = grp->gr_gid) == 0) + msg_fatal("file %s/%s: parameter %s: group %s has privileged group ID", + var_config_dir, MAIN_CONF_FILE, + VAR_SGID_GROUP, var_sgid_group); + + /* + * This detects only some forms of sharing. Enumerating the entire group + * file name space could be expensive. The purpose of this code is to + * discourage group ID sharing by developers and package maintainers. + */ + if ((grp = getgrgid(var_sgid_gid)) != 0 + && strcmp(grp->gr_name, var_sgid_group) != 0) + msg_fatal("file %s/%s: parameter %s: group %s has same group ID as %s", + var_config_dir, MAIN_CONF_FILE, + VAR_SGID_GROUP, var_sgid_group, grp->gr_name); +} + +/* check_overlap - disallow UID or GID sharing */ + +static void check_overlap(void) +{ + if (strcmp(var_default_privs, var_mail_owner) == 0) + msg_fatal("file %s/%s: parameters %s and %s specify the same user %s", + var_config_dir, MAIN_CONF_FILE, + VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER, + var_default_privs); + if (var_default_uid == var_owner_uid) + msg_fatal("file %s/%s: parameters %s and %s: users %s and %s have the same user ID: %ld", + var_config_dir, MAIN_CONF_FILE, + VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER, + var_default_privs, var_mail_owner, + (long) var_owner_uid); + if (var_default_gid == var_owner_gid) + msg_fatal("file %s/%s: parameters %s and %s: users %s and %s have the same group ID: %ld", + var_config_dir, MAIN_CONF_FILE, + VAR_DEFAULT_PRIVS, VAR_MAIL_OWNER, + var_default_privs, var_mail_owner, + (long) var_owner_gid); + if (var_default_gid == var_sgid_gid) + msg_fatal("file %s/%s: parameters %s and %s: user %s and group %s have the same group ID: %ld", + var_config_dir, MAIN_CONF_FILE, + VAR_DEFAULT_PRIVS, VAR_SGID_GROUP, + var_default_privs, var_sgid_group, + (long) var_sgid_gid); + if (var_owner_gid == var_sgid_gid) + msg_fatal("file %s/%s: parameters %s and %s: user %s and group %s have the same group ID: %ld", + var_config_dir, MAIN_CONF_FILE, + VAR_MAIL_OWNER, VAR_SGID_GROUP, + var_mail_owner, var_sgid_group, + (long) var_sgid_gid); } /* mail_params_init - configure built-in parameters */ @@ -270,7 +382,9 @@ void mail_params_init() }; static CONFIG_STR_TABLE other_str_defaults[] = { VAR_MAIL_NAME, DEF_MAIL_NAME, &var_mail_name, 1, 0, + VAR_SYSLOG_NAME, DEF_SYSLOG_NAME, &var_syslog_name, 1, 0, VAR_MAIL_OWNER, DEF_MAIL_OWNER, &var_mail_owner, 1, 0, + VAR_SGID_GROUP, DEF_SGID_GROUP, &var_sgid_group, 1, 0, VAR_MYDEST, DEF_MYDEST, &var_mydest, 0, 0, VAR_MYORIGIN, DEF_MYORIGIN, &var_myorigin, 1, 0, VAR_RELAYHOST, DEF_RELAYHOST, &var_relayhost, 0, 0, @@ -283,6 +397,7 @@ void mail_params_init() VAR_DOUBLE_BOUNCE, DEF_DOUBLE_BOUNCE, &var_double_bounce_sender, 1, 0, VAR_DEFAULT_PRIVS, DEF_DEFAULT_PRIVS, &var_default_privs, 1, 0, VAR_ALIAS_DB_MAP, DEF_ALIAS_DB_MAP, &var_alias_db_map, 0, 0, + VAR_MAIL_RELEASE, DEF_MAIL_RELEASE, &var_mail_release, 1, 0, VAR_MAIL_VERSION, DEF_MAIL_VERSION, &var_mail_version, 1, 0, VAR_DB_TYPE, DEF_DB_TYPE, &var_db_type, 1, 0, VAR_HASH_QUEUE_NAMES, DEF_HASH_QUEUE_NAMES, &var_hash_queue_names, 1, 0, @@ -293,6 +408,11 @@ void mail_params_init() VAR_IMPORT_ENVIRON, DEF_IMPORT_ENVIRON, &var_import_environ, 0, 0, VAR_DEF_TRANSPORT, DEF_DEF_TRANSPORT, &var_def_transport, 0, 0, VAR_MYNETWORKS_STYLE, DEF_MYNETWORKS_STYLE, &var_mynetworks_style, 1, 0, + VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0, + VAR_VERP_DELIMS, DEF_VERP_DELIMS, &var_verp_delims, 2, 2, + VAR_VERP_FILTER, DEF_VERP_FILTER, &var_verp_filter, 1, 0, + VAR_PAR_DOM_MATCH, DEF_PAR_DOM_MATCH, &var_par_dom_match, 0, 0, + VAR_CONFIG_DIRS, DEF_CONFIG_DIRS, &var_config_dirs, 0, 0, 0, }; static CONFIG_STR_FN_TABLE function_str_defaults_2[] = { @@ -307,6 +427,8 @@ void mail_params_init() VAR_HASH_QUEUE_DEPTH, DEF_HASH_QUEUE_DEPTH, &var_hash_queue_depth, 1, 0, VAR_FORK_TRIES, DEF_FORK_TRIES, &var_fork_tries, 1, 0, VAR_FLOCK_TRIES, DEF_FLOCK_TRIES, &var_flock_tries, 1, 0, + VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0, + VAR_FAULT_INJ_CODE, DEF_FAULT_INJ_CODE, &var_fault_inj_code, 0, 0, 0, }; static CONFIG_TIME_TABLE time_defaults[] = { @@ -318,6 +440,7 @@ void mail_params_init() VAR_FLOCK_DELAY, DEF_FLOCK_DELAY, &var_flock_delay, 1, 0, VAR_FLOCK_STALE, DEF_FLOCK_STALE, &var_flock_stale, 1, 0, VAR_DAEMON_TIMEOUT, DEF_DAEMON_TIMEOUT, &var_daemon_timeout, 1, 0, + VAR_IN_FLOW_DELAY, DEF_IN_FLOW_DELAY, &var_in_flow_delay, 0, 10, 0, }; static CONFIG_BOOL_TABLE bool_defaults[] = { @@ -326,6 +449,7 @@ void mail_params_init() VAR_OWNREQ_SPECIAL, DEF_OWNREQ_SPECIAL, &var_ownreq_special, 0, }; + const char *cp; /* * Extract syslog_facility early, so that from here on all errors are @@ -334,7 +458,8 @@ void mail_params_init() get_mail_conf_str_table(first_str_defaults); if (!msg_syslog_facility(var_syslog_facility)) - msg_fatal("unknown %s configuration parameter value: %s", + msg_fatal("file %s/%s: parameter %s: unrecognized value: %s", + var_config_dir, MAIN_CONF_FILE, VAR_SYSLOG_FACILITY, var_syslog_facility); /* @@ -343,10 +468,14 @@ void mail_params_init() * the domain. */ get_mail_conf_str_fn_table(function_str_defaults); - if (!valid_hostname(var_myhostname, DO_GRIPE) - || !valid_hostname(var_mydomain, DO_GRIPE)) - msg_fatal("main.cf configuration error: bad %s or %s parameter value", - VAR_MYHOSTNAME, VAR_MYDOMAIN); + if (!valid_hostname(var_myhostname, DO_GRIPE)) + msg_fatal("file %s/%s: parameter %s: bad parameter value: %s", + var_config_dir, MAIN_CONF_FILE, + VAR_MYHOSTNAME, var_myhostname); + if (!valid_hostname(var_mydomain, DO_GRIPE)) + msg_fatal("file %s/%s: parameter %s: bad parameter value: %s", + var_config_dir, MAIN_CONF_FILE, + VAR_MYDOMAIN, var_mydomain); /* * Variables that are needed by almost every program. @@ -357,6 +486,8 @@ void mail_params_init() get_mail_conf_time_table(time_defaults); check_default_privs(); check_mail_owner(); + check_sgid_group(); + check_overlap(); /* * Variables whose defaults are determined at runtime, after other @@ -377,8 +508,25 @@ void mail_params_init() time(&var_starttime); /* + * Export the syslog name so children can inherit and use it before they + * have initialized. + */ + if ((cp = safe_getenv(CONF_ENV_LOGTAG)) == 0 + || strcmp(cp, var_syslog_name) != 0) + if (setenv(CONF_ENV_LOGTAG, var_syslog_name, 1) < 0) + msg_fatal("setenv %s %s: %m", CONF_ENV_LOGTAG, var_syslog_name); + + /* * I have seen this happen just too often. */ if (strcasecmp(var_myhostname, var_relayhost) == 0) msg_fatal("myhostname == relayhost"); + + /* + * One more sanity check. + */ + if ((cp = verp_delims_verify(var_verp_delims)) != 0) + msg_fatal("file %s/%s: parameters %s and %s: %s", + var_config_dir, MAIN_CONF_FILE, + VAR_VERP_DELIMS, VAR_VERP_FILTER, cp); } diff --git a/gnu/dist/postfix/src/global/mail_params.h b/gnu/dist/postfix/src/global/mail_params.h index 294fd1fc5fd..4009dbdc2b5 100644 --- a/gnu/dist/postfix/src/global/mail_params.h +++ b/gnu/dist/postfix/src/global/mail_params.h @@ -49,6 +49,11 @@ extern char *var_mail_owner; extern uid_t var_owner_uid; extern gid_t var_owner_gid; +#define VAR_SGID_GROUP "setgid_group" +#define DEF_SGID_GROUP "postdrop" +extern char *var_sgid_group; +extern gid_t var_sgid_gid; + #define VAR_DEFAULT_PRIVS "default_privs" #define DEF_DEFAULT_PRIVS "nobody" extern char *var_default_privs; @@ -124,6 +129,17 @@ extern char *var_masq_domains; #define DEF_MASQ_EXCEPTIONS "" extern char *var_masq_exceptions; +#define MASQ_CLASS_ENV_FROM "envelope_sender" +#define MASQ_CLASS_ENV_RCPT "envelope_recipient" +#define MASQ_CLASS_HDR_FROM "header_sender" +#define MASQ_CLASS_HDR_RCPT "header_recipient" + +#define VAR_MASQ_CLASSES "masquerade_classes" +#define DEF_MASQ_CLASSES MASQ_CLASS_ENV_FROM ", " \ + MASQ_CLASS_HDR_FROM ", " \ + MASQ_CLASS_HDR_RCPT +extern char *var_masq_classes; + /* * Intranet versus internet. */ @@ -158,13 +174,13 @@ extern char *var_queue_dir; #define VAR_DAEMON_DIR "daemon_directory" #ifndef DEF_DAEMON_DIR -#define DEF_DAEMON_DIR "$program_directory" +#define DEF_DAEMON_DIR "/usr/libexec/postfix" #endif extern char *var_daemon_dir; #define VAR_COMMAND_DIR "command_directory" #ifndef DEF_COMMAND_DIR -#define DEF_COMMAND_DIR "$program_directory" +#define DEF_COMMAND_DIR "/usr/sbin" #endif extern char *var_command_dir; @@ -191,6 +207,10 @@ extern time_t var_starttime; #endif extern char *var_config_dir; +#define VAR_CONFIG_DIRS "alternate_config_directories" +#define DEF_CONFIG_DIRS "" +extern char *var_config_dirs; + /* * Preferred type of indexed files. The DEF_DB_TYPE macro value is system * dependent. It is defined in <sys_defs.h>. @@ -224,6 +244,10 @@ extern char *var_always_bcc; /* * What to put in the To: header when no recipients were disclosed. + * + * XXX 2822: When no recipient headers remain, a system should insert a Bcc: + * header without additional information. That is not so great given that + * MTAs routinely strip Bcc: headers from message headers. */ #define VAR_RCPT_WITHELD "undisclosed_recipients_header" #define DEF_RCPT_WITHELD "To: undisclosed-recipients:;" @@ -358,6 +382,10 @@ extern char *var_home_mailbox; #define DEF_MAILBOX_COMMAND "" extern char *var_mailbox_command; +#define VAR_MAILBOX_CMD_MAPS "mailbox_command_maps" +#define DEF_MAILBOX_CMD_MAPS "" +extern char *var_mailbox_cmd_maps; + #define VAR_MAILBOX_TRANSP "mailbox_transport" #define DEF_MAILBOX_TRANSP "" extern char *var_mailbox_transport; @@ -634,6 +662,9 @@ extern int var_hash_queue_depth; * determines how many recipient addresses the SMTP client sends along with * each message. Unfortunately, some mailers misbehave and disconnect (smap) * when given more recipients than they are willing to handle. + * + * XXX 2821: A mail system is supposed to use EHLO instead of HELO, and to fall + * back to HELO if EHLO is not supported. */ #define VAR_BESTMX_TRANSP "best_mx_transport" #define DEF_BESTMX_TRANSP "" @@ -688,7 +719,11 @@ extern bool var_ign_mx_lookup_err; extern bool var_skip_quit_resp; #define VAR_SMTP_ALWAYS_EHLO "smtp_always_send_ehlo" +#ifdef RFC821_SYNTAX #define DEF_SMTP_ALWAYS_EHLO 0 +#else +#define DEF_SMTP_ALWAYS_EHLO 1 +#endif extern bool var_smtp_always_ehlo; #define VAR_SMTP_NEVER_EHLO "smtp_never_send_ehlo" @@ -699,6 +734,22 @@ extern bool var_smtp_never_ehlo; #define DEF_SMTP_BIND_ADDR "" extern char *var_smtp_bind_addr; +#define VAR_SMTP_RAND_ADDR "smtp_randomize_addresses" +#define DEF_SMTP_RAND_ADDR 1 +extern bool var_smtp_rand_addr; + +#define VAR_SMTP_BREAK_LINES "smtp_break_lines" +#define DEF_SMTP_BREAK_LINES 1 +extern bool var_smtp_break_lines; + +#define VAR_SMTP_PIX_THRESH "smtp_pix_workaround_threshold_time" +#define DEF_SMTP_PIX_THRESH "500s" +extern int var_smtp_pix_thresh; + +#define VAR_SMTP_PIX_DELAY "smtp_pix_workaround_delay_time" +#define DEF_SMTP_PIX_DELAY "10s" +extern int var_smtp_pix_delay; + /* * SMTP server. The soft error limit determines how many errors an SMTP * client may make before we start to slow down; the hard error limit @@ -729,9 +780,17 @@ extern int var_smtpd_hard_erlim; extern int var_smtpd_err_sleep; #define VAR_SMTPD_JUNK_CMD "smtpd_junk_command_limit" -#define DEF_SMTPD_JUNK_CMD 1000 +#define DEF_SMTPD_JUNK_CMD 100 extern int var_smtpd_junk_cmd_limit; +#define VAR_SMTPD_HIST_THRSH "smtpd_history_flush_threshold" +#define DEF_SMTPD_HIST_THRSH 100 +extern int var_smtpd_hist_thrsh; + +#define VAR_SMTPD_NOOP_CMDS "smtpd_noop_commands" +#define DEF_SMTPD_NOOP_CMDS "" +extern char *var_smtpd_noop_cmds; + /* * SASL authentication support, SMTP server side. */ @@ -747,6 +806,12 @@ extern char *var_smtpd_sasl_opts; #define DEF_SMTPD_SASL_REALM "$myhostname" extern char *var_smtpd_sasl_realm; +#define VAR_SMTPD_SND_AUTH_MAPS "smtpd_sender_login_maps" +#define DEF_SMTPD_SND_AUTH_MAPS "" +extern char *var_smtpd_snd_auth_maps; + +#define REJECT_SENDER_LOGIN_MISMATCH "reject_sender_login_mismatch" + /* * SASL authentication support, SMTP client side. */ @@ -1092,6 +1157,10 @@ extern int var_relay_code; #define PERMIT_MX_BACKUP "permit_mx_backup" +#define VAR_PERM_MX_NETWORKS "permit_mx_backup_networks" +#define DEF_PERM_MX_NETWORKS "" +extern char *var_perm_mx_networks; + #define VAR_ACCESS_MAP_CODE "access_map_reject_code" #define DEF_ACCESS_MAP_CODE 554 extern int var_access_map_code; @@ -1102,13 +1171,15 @@ extern int var_access_map_code; #define CHECK_RECIP_ACL "check_recipient_access" #define CHECK_ETRN_ACL "check_etrn_access" +#define WARN_IF_REJECT "warn_if_reject" + #define REJECT_MAPS_RBL "reject_maps_rbl" #define VAR_MAPS_RBL_CODE "maps_rbl_reject_code" #define DEF_MAPS_RBL_CODE 554 extern int var_maps_rbl_code; #define VAR_MAPS_RBL_DOMAINS "maps_rbl_domains" -#define DEF_MAPS_RBL_DOMAINS "blackholes.mail-abuse.org" +#define DEF_MAPS_RBL_DOMAINS "" extern char *var_maps_rbl_domains; #define VAR_SMTPD_DELAY_REJECT "smtpd_delay_reject" @@ -1117,6 +1188,10 @@ extern int var_smtpd_delay_reject; #define REJECT_UNAUTH_PIPE "reject_unauth_pipelining" +#define VAR_SMTPD_NULL_KEY "smtpd_null_access_lookup_key" +#define DEF_SMTPD_NULL_KEY "" +extern char *var_smtpd_null_key; + /* * Heuristic to reject most unknown recipients at the SMTP port. */ @@ -1192,7 +1267,7 @@ extern int var_fflush_refresh; * and what Postfix exports to the external world. */ #define VAR_IMPORT_ENVIRON "import_environment" -#define DEF_IMPORT_ENVIRON "MAIL_CONFIG MAIL_DEBUG TZ XAUTHORITY DISPLAY" +#define DEF_IMPORT_ENVIRON "MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY DISPLAY" extern char *var_import_environ; #define VAR_EXPORT_ENVIRON "export_environment" @@ -1230,6 +1305,112 @@ extern int var_virt_mailbox_limit; #define DEF_VIRT_MAILBOX_LOCK "fcntl" extern char *var_virt_mailbox_lock; + /* + * Distinct logging tag for multiple Postfix instances. + */ +#define VAR_SYSLOG_NAME "syslog_name" +#define DEF_SYSLOG_NAME "postfix" +extern char *var_syslog_name; + + /* + * QMQPD + */ +#define VAR_QMQPD_CLIENTS "qmqpd_authorized_clients" +#define DEF_QMQPD_CLIENTS "" +extern char *var_qmqpd_clients; + +#define VAR_QMTPD_TMOUT "qmqpd_timeout" +#define DEF_QMTPD_TMOUT "300s" +extern int var_qmqpd_timeout; + +#define VAR_QMTPD_ERR_SLEEP "qmqpd_error_delay" +#define DEF_QMTPD_ERR_SLEEP "5s" +extern int var_qmqpd_err_sleep; + + /* + * VERP, more DJB intellectual cross-pollination. However, we prefer + as + * the default recipient delimiter. + */ +#define VAR_VERP_DELIMS "default_verp_delimiters" +#define DEF_VERP_DELIMS "+=" +extern char *var_verp_delims; + +#define VAR_VERP_FILTER "verp_delimiter_filter" +#define DEF_VERP_FILTER "-=+" +extern char *var_verp_filter; + +#define VAR_VERP_BOUNCE_OFF "disable_verp_bounces" +#define DEF_VERP_BOUNCE_OFF 0 +extern bool var_verp_bounce_off; + + /* + * Inbound mail flow control. This allows for a stiffer coupling between + * receiving mail and sending mail. A sending process produces one token for + * each message that it takes from the incoming queue; a receiving process + * consumes one token for each message that it adds to the incoming queue. + * When no token is available (Postfix receives more mail than it is able to + * deliver) a receiving process pauses for $in_flow_delay seconds so that + * the sending processes get a chance to access the disk. + */ +#define VAR_IN_FLOW_DELAY "in_flow_delay" +#define DEF_IN_FLOW_DELAY "1s" +extern int var_in_flow_delay; + + /* + * Backwards compatibility: foo.com matches itself and names below foo.com. + */ +#define VAR_PAR_DOM_MATCH "parent_domain_matches_subdomains" +#define DEF_PAR_DOM_MATCH VAR_DEBUG_PEER_LIST "," \ + VAR_FFLUSH_DOMAINS "," \ + VAR_MYNETWORKS "," \ + VAR_PERM_MX_NETWORKS "," \ + VAR_QMQPD_CLIENTS "," \ + VAR_RELAY_DOMAINS "," \ + SMTPD_ACCESS_MAPS +extern char *var_par_dom_match; + +#define SMTPD_ACCESS_MAPS "smtpd_access_maps" + + /* + * Run-time fault injection. + */ +#define VAR_FAULT_INJ_CODE "fault_injection_code" +#define DEF_FAULT_INJ_CODE 0 +extern int var_fault_inj_code; + + /* + * Install/upgrade information. + */ +#define VAR_SENDMAIL_PATH "sendmail_path" +#ifndef DEF_SENDMAIL_PATH +#define DEF_SENDMAIL_PATH "/usr/sbin/sendmail" +#endif + +#define VAR_MAILQ_PATH "mailq_path" +#ifndef DEF_MAILQ_PATH +#define DEF_MAILQ_PATH "/usr/bin/mailq" +#endif + +#define VAR_NEWALIAS_PATH "newaliases_path" +#ifndef DEF_NEWALIAS_PATH +#define DEF_NEWALIAS_PATH "/usr/bin/newaliases" +#endif + +#define VAR_MANPAGE_DIR "manpage_directory" +#ifndef DEF_MANPAGE_DIR +#define DEF_MANPAGE_DIR "/usr/local/man" +#endif + +#define VAR_SAMPLE_DIR "sample_directory" +#ifndef DEF_SAMPLE_DIR +#define DEF_SAMPLE_DIR DEF_CONFIG_DIR +#endif + +#define VAR_README_DIR "readme_directory" +#ifndef DEF_README_DIR +#define DEF_README_DIR "no" +#endif + /* LICENSE /* .ad /* .fi diff --git a/gnu/dist/postfix/src/global/mail_proto.h b/gnu/dist/postfix/src/global/mail_proto.h index 78b76c864ec..adadcda60c9 100644 --- a/gnu/dist/postfix/src/global/mail_proto.h +++ b/gnu/dist/postfix/src/global/mail_proto.h @@ -21,6 +21,7 @@ */ #include <vstream.h> #include <iostuff.h> +#include <attr.h> /* * Names of services: these are the names if INET ports, UNIX-domain sockets @@ -50,12 +51,6 @@ #define MAIL_CLASS_PRIVATE "private" /* - * When sending across a list of objects, this is how we signal the list - * end. - */ -#define MAIL_EOF "@" - - /* * Generic triggers. */ #define TRIGGER_REQ_WAKEUP 'W' /* wakeup */ @@ -71,28 +66,37 @@ /* * Functional interface. */ -#define MAIL_SCAN_MORE 0 -#define MAIL_SCAN_DONE 1 -#define MAIL_SCAN_ERROR -1 - -typedef int (*MAIL_SCAN_FN) (const char *, char *); -typedef void (*MAIL_PRINT_FN) (VSTREAM *, const char *); extern VSTREAM *mail_connect(const char *, const char *, int); extern VSTREAM *mail_connect_wait(const char *, const char *); -extern int mail_scan(VSTREAM *, const char *,...); -extern void mail_scan_register(int, const char *, MAIL_SCAN_FN); -extern void mail_print_register(int, const char *, MAIL_PRINT_FN); -extern int PRINTFLIKE(2, 3) mail_print(VSTREAM *, const char *,...); -extern int PRINTFLIKE(3, 4) mail_command_write(const char *, const char *, const char *,...); -extern int mail_command_read(VSTREAM *, char *,...); +extern int mail_command_client(const char *, const char *,...); +extern int mail_command_server(VSTREAM *,...); extern int mail_trigger(const char *, const char *, const char *, int); extern char *mail_pathname(const char *, const char *); /* - * Stuff that needs <stdarg.h> + * Attribute names. */ -extern int mail_vprint(VSTREAM *, const char *, va_list); -extern int mail_vscan(VSTREAM *, const char *, va_list); +#define MAIL_ATTR_REQ "request" +#define MAIL_ATTR_NREQ "nrequest" +#define MAIL_ATTR_STATUS "status" + +#define MAIL_ATTR_FLAGS "flags" +#define MAIL_ATTR_QUEUE "queue_name" +#define MAIL_ATTR_QUEUEID "queue_id" +#define MAIL_ATTR_SENDER "sender" +#define MAIL_ATTR_RECIP "recipient" +#define MAIL_ATTR_WHY "reason" +#define MAIL_ATTR_VERPDL "verp_delimiters" +#define MAIL_ATTR_SITE "site" +#define MAIL_ATTR_OFFSET "offset" +#define MAIL_ATTR_SIZE "size" +#define MAIL_ATTR_ERRTO "errors-to" +#define MAIL_ATTR_RRCPT "return-receipt" +#define MAIL_ATTR_TIME "time" +#define MAIL_ATTR_RULE "rule" +#define MAIL_ATTR_ADDR "address" +#define MAIL_ATTR_TRANSPORT "transport" +#define MAIL_ATTR_NEXTHOP "nexthop" /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/global/mail_queue.c b/gnu/dist/postfix/src/global/mail_queue.c index 5fc18640ec3..54916c8b59f 100644 --- a/gnu/dist/postfix/src/global/mail_queue.c +++ b/gnu/dist/postfix/src/global/mail_queue.c @@ -76,7 +76,7 @@ /* mail_queue_rename() renames a queue file. A non-zero result /* means the operation failed. /* -/* mail_queue_remove() renames the named queue file. A non-zero result +/* mail_queue_remove() removes the named queue file. A non-zero result /* means the operation failed. /* /* mail_queue_name_ok() validates a mail queue name and returns @@ -378,9 +378,23 @@ VSTREAM *mail_queue_enter(const char *queue_name, int mode) file_id = get_file_id(fd); GETTIMEOFDAY(&tv); + /* + * XXX Some systems seem to have clocks that correlate with process + * scheduling or something. Unfortunately, we cannot add random + * quantities to the time, because the non-inode part of a queue ID must + * not repeat within the same second. The queue ID is the sole thing that + * prevents multiple messages from getting the same Message-ID value. + */ for (count = 0;; count++) { vstring_sprintf(id_buf, "%05X%s", (int) tv.tv_usec, file_id); mail_queue_path(path_buf, queue_name, STR(id_buf)); +#if 0 + if (access(STR(path_buf), X_OK) == 0) { /* collision. */ + if ((int) ++tv.tv_usec < 0) + tv.tv_usec = 0; + continue; + } +#endif if (sane_rename(STR(temp_path), STR(path_buf)) == 0) /* success */ break; if (errno == EPERM || errno == EISDIR) {/* collision. weird. */ diff --git a/gnu/dist/postfix/src/global/mail_stream.c b/gnu/dist/postfix/src/global/mail_stream.c index e30537dfff6..74a8ada8c0a 100644 --- a/gnu/dist/postfix/src/global/mail_stream.c +++ b/gnu/dist/postfix/src/global/mail_stream.c @@ -14,10 +14,11 @@ /* .in -4 /* } MAIL_STREAM; /* -/* MAIL_STREAM *mail_stream_file(queue, class, service) +/* MAIL_STREAM *mail_stream_file(queue, class, service, mode) /* const char *queue; /* const char *class; /* const char *service; +/* int mode; /* /* MAIL_STREAM *mail_stream_service(class, service) /* const char *class; @@ -29,8 +30,9 @@ /* void mail_stream_cleanup(info) /* MAIL_STREAM *info; /* -/* int mail_stream_finish(info) +/* int mail_stream_finish(info, why) /* MAIL_STREAM *info; +/* VSTRING *why; /* DESCRIPTION /* This module provides a generic interface to Postfix queue file /* format messages to file, to Postfix server, or to external command. @@ -41,7 +43,8 @@ /* /* mail_stream_file() opens a mail stream to a newly-created file and /* arranges for trigger delivery at finish time. This call never fails. -/* But it may take forever. +/* But it may take forever. The mode argument specifies additional +/* file permissions that will be OR-ed in. /* /* mail_stream_command() opens a mail stream to external command, /* and receives queue ID information from the command. The result @@ -62,6 +65,7 @@ /* any of the mail_stream_xxx() routines, and destroys the argument. /* The result is any of the status codes defined in <cleanup_user.h>. /* It is up to the caller to remove incomplete file objects. +/* The why argument can be a null pointer. /* LICENSE /* .ad /* .fi @@ -78,6 +82,7 @@ #include <sys_defs.h> #include <sys/stat.h> #include <unistd.h> +#include <errno.h> /* Utility library. */ @@ -116,22 +121,30 @@ void mail_stream_cleanup(MAIL_STREAM * info) /* mail_stream_finish_file - finish file mail stream */ -static int mail_stream_finish_file(MAIL_STREAM * info) +static int mail_stream_finish_file(MAIL_STREAM * info, VSTRING *unused_why) { int status = 0; static char wakeup[] = {TRIGGER_REQ_WAKEUP}; /* * Make sure the message makes it to file. Set the execute bit when no - * write error was detected. + * write error was detected. Some people believe that this code has a + * problem if the system crashes before fsync() returns; fchmod() could + * take effect before all the data blocks are written. Wietse claims that + * this is not a problem. Postfix rejects incomplete queue files, even + * when the +x attribute is set. Every Postfix queue file record has a + * type code and a length field. Files with truncated records are + * rejected, as are files with unknown type codes. Every Postfix queue + * file must end with an explicit END record. Postfix queue files without + * END record are discarded. */ if (vstream_fflush(info->stream) - || fchmod(vstream_fileno(info->stream), 0700) + || fchmod(vstream_fileno(info->stream), 0700 | info->mode) #ifdef HAS_FSYNC || fsync(vstream_fileno(info->stream)) #endif ) - status = CLEANUP_STAT_WRITE; + status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); /* * Close the queue file and mark it as closed. Be prepared for @@ -142,7 +155,7 @@ static int mail_stream_finish_file(MAIL_STREAM * info) * reasons. */ if (info->close(info->stream)) - status = CLEANUP_STAT_WRITE; + status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE); info->stream = 0; /* @@ -161,14 +174,20 @@ static int mail_stream_finish_file(MAIL_STREAM * info) /* mail_stream_finish_ipc - finish IPC mail stream */ -static int mail_stream_finish_ipc(MAIL_STREAM * info) +static int mail_stream_finish_ipc(MAIL_STREAM * info, VSTRING *why) { int status = CLEANUP_STAT_WRITE; /* * Receive the peer's completion status. */ - if (mail_scan(info->stream, "%d", &status) != 1) + if ((why && attr_scan(info->stream, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_STR, MAIL_ATTR_WHY, why, + ATTR_TYPE_END) != 2) + || (!why && attr_scan(info->stream, ATTR_FLAG_MISSING, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_END) != 1)) status = CLEANUP_STAT_WRITE; /* @@ -180,20 +199,20 @@ static int mail_stream_finish_ipc(MAIL_STREAM * info) /* mail_stream_finish - finish action */ -int mail_stream_finish(MAIL_STREAM * info) +int mail_stream_finish(MAIL_STREAM * info, VSTRING *why) { - return (info->finish(info)); + return (info->finish(info, why)); } /* mail_stream_file - destination is file */ MAIL_STREAM *mail_stream_file(const char *queue, const char *class, - const char *service) + const char *service, int mode) { MAIL_STREAM *info; VSTREAM *stream; - stream = mail_queue_enter(queue, 0600); + stream = mail_queue_enter(queue, 0600 | mode); if (msg_verbose) msg_info("open %s", VSTREAM_PATH(stream)); @@ -204,6 +223,7 @@ MAIL_STREAM *mail_stream_file(const char *queue, const char *class, info->id = mystrdup(basename(VSTREAM_PATH(stream))); info->class = mystrdup(class); info->service = mystrdup(service); + info->mode = mode; return (info); } @@ -218,7 +238,8 @@ MAIL_STREAM *mail_stream_service(const char *class, const char *name) id_buf = vstring_alloc(10); stream = mail_connect_wait(class, name); - if (mail_scan(stream, "%s", id_buf) != 1) { + if (attr_scan(stream, ATTR_FLAG_MISSING, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id_buf, 0) != 1) { vstream_fclose(stream); return (0); } else { @@ -263,8 +284,12 @@ MAIL_STREAM *mail_stream_command(const char *command) sleep(10); } argv_free(export_env); + vstream_control(stream, + VSTREAM_CTL_PATH, command, + VSTREAM_CTL_END); - if (mail_scan(stream, "%s", id_buf) != 1) { + if (attr_scan(stream, ATTR_FLAG_MISSING, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id_buf, 0) != 1) { vstream_pclose(stream); return (0); } else { diff --git a/gnu/dist/postfix/src/global/mail_stream.h b/gnu/dist/postfix/src/global/mail_stream.h index 3ae6c624ac8..c9fbfd97a78 100644 --- a/gnu/dist/postfix/src/global/mail_stream.h +++ b/gnu/dist/postfix/src/global/mail_stream.h @@ -22,7 +22,7 @@ */ typedef struct MAIL_STREAM MAIL_STREAM; -typedef int (*MAIL_STREAM_FINISH_FN) (MAIL_STREAM *); +typedef int (*MAIL_STREAM_FINISH_FN) (MAIL_STREAM *, VSTRING *); typedef int (*MAIL_STREAM_CLOSE_FN) (VSTREAM *); struct MAIL_STREAM { @@ -32,13 +32,14 @@ struct MAIL_STREAM { MAIL_STREAM_CLOSE_FN close; /* close stream */ char *class; /* trigger class */ char *service; /* trigger service */ + int mode; /* additional permissions */ }; -extern MAIL_STREAM *mail_stream_file(const char *, const char *, const char *); +extern MAIL_STREAM *mail_stream_file(const char *, const char *, const char *, int); extern MAIL_STREAM *mail_stream_service(const char *, const char *); extern MAIL_STREAM *mail_stream_command(const char *); extern void mail_stream_cleanup(MAIL_STREAM *); -extern int mail_stream_finish(MAIL_STREAM *); +extern int mail_stream_finish(MAIL_STREAM *, VSTRING *); /* LICENSE diff --git a/gnu/dist/postfix/src/global/mail_task.c b/gnu/dist/postfix/src/global/mail_task.c index 0500754565c..2c06f9edaa3 100644 --- a/gnu/dist/postfix/src/global/mail_task.c +++ b/gnu/dist/postfix/src/global/mail_task.c @@ -12,7 +12,8 @@ /* mail_task() enforces consistent naming of mailer processes. /* It strips pathname information from the process name, and /* prepends the name of the mail system so that logfile entries -/* are easier to recognize. +/* are easier to recognize. The mail system name is specified +/* with the "syslog_name" configuration parameter. /* /* The result is volatile. Make a copy of the result if it is /* to be used for any appreciable amount of time. @@ -35,25 +36,28 @@ /* Utility library. */ #include <vstring.h> +#include <safe.h> /* Global library. */ #include "mail_params.h" +#include "mail_conf.h" #include "mail_task.h" -#define MAIL_TASK_FORMAT "postfix/%s" - /* mail_task - clean up and decorate the process name */ const char *mail_task(const char *argv0) { static VSTRING *canon_name; const char *slash; + const char *tag; if (canon_name == 0) canon_name = vstring_alloc(10); if ((slash = strrchr(argv0, '/')) != 0) argv0 = slash + 1; - vstring_sprintf(canon_name, MAIL_TASK_FORMAT, argv0); + if ((tag = safe_getenv(CONF_ENV_LOGTAG)) == 0) + tag = DEF_SYSLOG_NAME; + vstring_sprintf(canon_name, "%s/%s", tag, argv0); return (vstring_str(canon_name)); } diff --git a/gnu/dist/postfix/src/global/mail_trigger.c b/gnu/dist/postfix/src/global/mail_trigger.c index 4d4d61c3f8b..457f19d6b8f 100644 --- a/gnu/dist/postfix/src/global/mail_trigger.c +++ b/gnu/dist/postfix/src/global/mail_trigger.c @@ -79,7 +79,7 @@ int mail_trigger(const char *class, const char *service, */ path = mail_pathname(class, service); if ((status = stat(path, &st)) < 0) { - /* void */ ; + msg_warn("unable to look up %s: %m", path); } else if (S_ISFIFO(st.st_mode)) { status = fifo_trigger(path, req_buf, req_len, var_trigger_timeout); if (status < 0 && S_ISSOCK(st.st_mode)) diff --git a/gnu/dist/postfix/src/global/mail_version.h b/gnu/dist/postfix/src/global/mail_version.h index fe062b14530..c5c930804ee 100644 --- a/gnu/dist/postfix/src/global/mail_version.h +++ b/gnu/dist/postfix/src/global/mail_version.h @@ -12,12 +12,29 @@ /* .nf /* - * Version of this program. + * Version of this program. Official versions are called a.b.c, and + * snapshots are called a.b.c-yyyymmdd, where a=major release number, + * b=minor release number, c=patchlevel, and yyyymmdd is the release date: + * yyyy=year, mm=month, dd=day. + * + * Patches change the patchlevel and the release date. Snapshots change the + * release date only, unless they include the same bugfix as a patch release. */ #define VAR_MAIL_VERSION "mail_version" -#define DEF_MAIL_VERSION "Postfix-20010228-pl08" +#ifdef SNAPSHOT +#define DEF_MAIL_VERSION "1.1.2-$mail_release_date" +#else +#define DEF_MAIL_VERSION "1.1.2" +#endif extern char *var_mail_version; + /* + * Release date. + */ +#define VAR_MAIL_RELEASE "mail_release_date" +#define DEF_MAIL_RELEASE "20020125" +extern char *var_mail_release; + /* LICENSE /* .ad /* .fi diff --git a/gnu/dist/postfix/src/global/mark_corrupt.c b/gnu/dist/postfix/src/global/mark_corrupt.c index 994492ef1c3..bcffef8dd5c 100644 --- a/gnu/dist/postfix/src/global/mark_corrupt.c +++ b/gnu/dist/postfix/src/global/mark_corrupt.c @@ -27,15 +27,19 @@ #include <sys_defs.h> #include <sys/stat.h> +#include <unistd.h> /* Utility library. */ #include <msg.h> #include <vstream.h> +#include <set_eugid.h> /* Global library. */ #include <mail_queue.h> +#include <mail_params.h> +#include <deliver_request.h> #include <mark_corrupt.h> /* mark_corrupt - mark queue file as corrupt */ @@ -43,6 +47,16 @@ int mark_corrupt(VSTREAM *src) { char *myname = "mark_corrupt"; + uid_t saved_uid; + gid_t saved_gid; + + /* + * If not running as the mail system, change privileges first. + */ + if ((saved_uid = geteuid()) != var_owner_uid) { + saved_gid = getegid(); + set_eugid(var_owner_uid, var_owner_gid); + } /* * For now, the result value is -1; this may become a bit mask, or @@ -52,5 +66,12 @@ int mark_corrupt(VSTREAM *src) msg_warn("corrupted queue file: %s", VSTREAM_PATH(src)); if (fchmod(vstream_fileno(src), MAIL_QUEUE_STAT_CORRUPT)) msg_fatal("%s: fchmod %s: %m", myname, VSTREAM_PATH(src)); - return (-1); + + /* + * Restore privileges. + */ + if (saved_uid != var_owner_uid) + set_eugid(saved_uid, saved_gid); + + return (DEL_STAT_DEFER); } diff --git a/gnu/dist/postfix/src/global/match_parent_style.c b/gnu/dist/postfix/src/global/match_parent_style.c new file mode 100644 index 00000000000..a914f616fbe --- /dev/null +++ b/gnu/dist/postfix/src/global/match_parent_style.c @@ -0,0 +1,72 @@ +/*++ +/* NAME +/* match_parent_style 3 +/* SUMMARY +/* parent domain matching control +/* SYNOPSIS +/* #include <match_parent_style.h> +/* +/* int match_parent_style(name) +/* const char *name; +/* DESCRIPTION +/* This module queries configuration parameters for the policy that +/* controls how wild-card parent domain names are used by various +/* postfix lookup mechanisms. +/* +/* match_parent_style() looks up "name" in the +/* parent_domain_matches_subdomain configuration parameter +/* and returns either MATCH_FLAG_PARENT or MATCH_PARENT_NONE. +/* DIAGNOSTICS +/* Fatal error: out of memory, name listed under both parent wild card +/* matching policies. +/* SEE ALSO +/* string_list(3) plain string matching +/* domain_list(3) match host name patterns +/* namadr_list(3) match host name/address patterns +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +/* Global library. */ + +#include <string_list.h> +#include <mail_params.h> +#include <match_parent_style.h> + +/* Application-specific. */ + +static STRING_LIST *match_par_dom_list; + +int match_parent_style(const char *name) +{ + int result; + + /* + * Initialize on the fly. + */ + if (match_par_dom_list == 0) + match_par_dom_list = + string_list_init(MATCH_FLAG_NONE, var_par_dom_match); + + /* + * Look up the parent domain matching policy. + */ + if (string_list_match(match_par_dom_list, name)) + result = MATCH_FLAG_PARENT; + else + result = 0; + return (result); +} diff --git a/gnu/dist/postfix/src/global/match_parent_style.h b/gnu/dist/postfix/src/global/match_parent_style.h new file mode 100644 index 00000000000..7b51eddf70a --- /dev/null +++ b/gnu/dist/postfix/src/global/match_parent_style.h @@ -0,0 +1,35 @@ +#ifndef _MATCH_PARENT_STYLE_H_INCLUDED_ +#define _MATCH_PARENT_STYLE_H_INCLUDED_ + +/*++ +/* NAME +/* match_parent_style 3h +/* SUMMARY +/* parent domain matching control +/* SYNOPSIS +/* #include <match_parent_style.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <match_ops.h> + + /* + * External interface. + */ +extern int match_parent_style(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/global/namadr_list.c b/gnu/dist/postfix/src/global/namadr_list.c index 0e6b1bba730..c5169d87e59 100644 --- a/gnu/dist/postfix/src/global/namadr_list.c +++ b/gnu/dist/postfix/src/global/namadr_list.c @@ -6,7 +6,8 @@ /* SYNOPSIS /* #include <namadr_list.h> /* -/* NAMADR_LIST *namadr_list_init(pattern_list) +/* NAMADR_LIST *namadr_list_init(flags, pattern_list) +/* int flags; /* const char *pattern_list; /* /* int namadr_list_match(list, name, addr) @@ -17,6 +18,8 @@ /* void namadr_list_free(list) /* NAMADR_LIST *list; /* DESCRIPTION +/* This is a convenience wrapper around the match_list module. +/* /* This module implements tests for list membership of a /* hostname or network address. /* @@ -35,9 +38,18 @@ /* a pattern, or when any of its parent domains matches a /* pattern. The matching process is case insensitive. /* -/* namadr_list_init() performs initializations. The argument -/* is a list of patterns, or the absolute pathname of a file -/* with patterns. +/* namadr_list_init() performs initializations. The first +/* argument is the bit-wise OR of zero or more of the +/* following: +/* .RS +/* .IP MATCH_FLAG_PARENT +/* The hostname pattern foo.com matches itself and any name below +/* the domain foo.com. If this flag is cleared, foo.com matches itself +/* only, and .foo.com matches any name below the domain foo.com. +/* .RE +/* Specify MATCH_FLAG_NONE to request none of the above. +/* The second argument is a list of patterns, or the absolute +/* pathname of a file with patterns. /* /* namadr_list_match() matches the specified host name and /* address against the specified list of patterns. @@ -67,33 +79,11 @@ /* Utility library. */ #include <match_list.h> -#include <match_ops.h> /* Global library. */ #include "namadr_list.h" -/* namadr_list_init - initialize domain list */ - -NAMADR_LIST *namadr_list_init(const char *patterns) -{ - return (match_list_init(patterns, 2, match_hostaddr, match_hostname)); -} - -/* namadr_list_match - match host against set of namadr_list patterns */ - -int namadr_list_match(NAMADR_LIST *list, const char *name, const char *addr) -{ - return (match_list_match(list, addr, name)); -} - -/* namadr_list_free - release storage */ - -void namadr_list_free(NAMADR_LIST *list) -{ - match_list_free(list); -} - #ifdef TEST #include <msg.h> @@ -126,7 +116,7 @@ main(int argc, char **argv) } if (argc != optind + 3) usage(argv[0]); - list = namadr_list_init(argv[optind]); + list = namadr_list_init(MATCH_FLAG_PARENT, argv[optind]); host = argv[optind + 1]; addr = argv[optind + 2]; vstream_printf("%s/%s: %s\n", host, addr, diff --git a/gnu/dist/postfix/src/global/namadr_list.h b/gnu/dist/postfix/src/global/namadr_list.h index 186cd66c1b7..9bf90d7684d 100644 --- a/gnu/dist/postfix/src/global/namadr_list.h +++ b/gnu/dist/postfix/src/global/namadr_list.h @@ -7,18 +7,25 @@ /* SUMMARY /* name/address membership /* SYNOPSIS -/* #include <namadr_list_list.h> +/* #include <namadr_list.h> /* DESCRIPTION /* .nf /* + * Utility library. + */ +#include <match_list.h> +#include <match_ops.h> + + /* * External interface. */ -typedef struct MATCH_LIST NAMADR_LIST; +#define NAMADR_LIST MATCH_LIST -extern NAMADR_LIST *namadr_list_init(const char *); -extern int namadr_list_match(NAMADR_LIST *, const char *, const char *); -extern void namadr_list_free(NAMADR_LIST *); +#define namadr_list_init(f, p) \ + match_list_init((f), (p), 2, match_hostname, match_hostaddr) +#define namadr_list_match match_list_match +#define namadr_list_free match_list_free /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/global/off_cvt.c b/gnu/dist/postfix/src/global/off_cvt.c index 95a689f14e1..756b63c8c60 100644 --- a/gnu/dist/postfix/src/global/off_cvt.c +++ b/gnu/dist/postfix/src/global/off_cvt.c @@ -27,10 +27,6 @@ /* Buffer for storage of the result of conversion to string. /* .IP offset /* Non-negative off_t value to be converted to string. -/* BUGS -/* The string to number conversion routine has no reliable way to -/* detect an overflow error, so the result may be much smaller -/* than the number specified in the input. /* DIAGNOSTICS /* Panic: negative offset /* LICENSE @@ -70,19 +66,28 @@ off_t off_cvt_string(const char *str) { int ch; off_t result; + off_t res2; + off_t res4; + off_t res8; + off_t res10; /* - * We're not doing this often, so simplicity has precedence over - * performance. XXX Need a portable way to correctly detect overflow. - * Bear in mind that an off_t is not necessarily a long integer, so using - * raw bit patterns is not going to be a portable solution. + * Multiplication by numbers > 2 can overflow without producing a smaller + * result mod 2^N (where N is the number of bits in the result type). + * (Victor Duchovni, Morgan Stanley). */ for (result = 0; (ch = *(unsigned char *) str) != 0; str++) { if (!ISDIGIT(ch)) return (-1); - result *= 10; - result += ch - '0'; - if (result < 0) + if ((res2 = result + result) < result) + return (-1); + if ((res4 = res2 + res2) < res2) + return (-1); + if ((res8 = res4 + res4) < res4) + return (-1); + if ((res10 = res8 + res2) < res8) + return (-1); + if ((result = res10 + ch - '0') < res10) return (-1); } return (result); @@ -101,7 +106,7 @@ VSTRING *off_cvt_number(VSTRING *buf, off_t offset) * Sanity checks */ if (offset < 0) - msg_panic("off_cvt_number: negative offset %s", + msg_panic("off_cvt_number: negative offset -%s", STR(off_cvt_number(buf, -offset))); /* diff --git a/gnu/dist/postfix/src/global/pipe_command.c b/gnu/dist/postfix/src/global/pipe_command.c index 8c9615a6fd4..11f68b32544 100644 --- a/gnu/dist/postfix/src/global/pipe_command.c +++ b/gnu/dist/postfix/src/global/pipe_command.c @@ -33,11 +33,11 @@ /* or when it appears to be a shell built-in command, otherwise /* the command is executed without invoking a shell. /* One of PIPE_CMD_COMMAND or PIPE_CMD_ARGV must be specified. +/* See also the PIPE_CMD_SHELL attribute below. /* .IP "PIPE_CMD_ARGV (char **)" /* The command is specified as an argument vector. This vector is /* passed without further inspection to the \fIexecvp\fR() routine. /* One of PIPE_CMD_COMMAND or PIPE_CMD_ARGV must be specified. -/* See also the PIPE_CMD_SHELL attribute below. /* .IP "PIPE_CMD_ENV (char **)" /* Additional environment information, in the form of a null-terminated /* list of name, value, name, value, ... elements. By default only the @@ -88,8 +88,11 @@ /* The command indicated that the message was not acceptable, /* or the command did not finish within the time limit. /* The reason is given via the \fIwhy\fR argument. +/* .IP PIPE_STAT_CORRUPT +/* The queue file is corrupted. /* SEE ALSO /* mail_copy(3) deliver to any. +/* mark_corrupt(3) mark queue file as corrupt. /* sys_exits(3) sendmail-compatible exit status codes. /* LICENSE /* .ad @@ -509,6 +512,8 @@ int pipe_command(VSTREAM *src, VSTRING *why,...) log_len ? ". Command output: " : "", log_buf); return (PIPE_STAT_BOUNCE); } + } else if (write_status & MAIL_COPY_STAT_CORRUPT) { + return (PIPE_STAT_CORRUPT); } else if (write_status && errno != EPIPE) { vstring_sprintf(why, "Command failed: %m: \"%s\"", args.command); return (PIPE_STAT_DEFER); diff --git a/gnu/dist/postfix/src/global/pipe_command.h b/gnu/dist/postfix/src/global/pipe_command.h index cf8d5989220..1750ccc7bd3 100644 --- a/gnu/dist/postfix/src/global/pipe_command.h +++ b/gnu/dist/postfix/src/global/pipe_command.h @@ -45,6 +45,7 @@ #define PIPE_STAT_OK 0 /* success */ #define PIPE_STAT_DEFER 1 /* try again */ #define PIPE_STAT_BOUNCE 2 /* failed */ +#define PIPE_STAT_CORRUPT 3 /* corrupted file */ extern int pipe_command(VSTREAM *, VSTRING *,...); diff --git a/gnu/dist/postfix/src/global/post_mail.c b/gnu/dist/postfix/src/global/post_mail.c index c3673243f5c..7c2fb0def1e 100644 --- a/gnu/dist/postfix/src/global/post_mail.c +++ b/gnu/dist/postfix/src/global/post_mail.c @@ -6,17 +6,15 @@ /* SYNOPSIS /* #include <post_mail.h> /* -/* VSTREAM *post_mail_fopen(sender, recipient, flags, via) +/* VSTREAM *post_mail_fopen(sender, recipient, flags) /* const char *sender; /* const char *recipient; /* int flags; -/* const char *via; /* -/* VSTREAM *post_mail_fopen_nowait(sender, recipient, flags, via) +/* VSTREAM *post_mail_fopen_nowait(sender, recipient, flags) /* const char *sender; /* const char *recipient; /* int flags; -/* const char *via; /* /* int post_mail_fprintf(stream, format, ...) /* VSTREAM *stream; @@ -132,7 +130,7 @@ /* post_mail_init - initial negotiations */ static void post_mail_init(VSTREAM *stream, const char *sender, - const char *recipient, int flags, const char *via) + const char *recipient, int flags) { VSTRING *id = vstring_alloc(100); long now = time((time_t *) 0); @@ -141,8 +139,12 @@ static void post_mail_init(VSTREAM *stream, const char *sender, /* * Negotiate with the cleanup service. Give up if we can't agree. */ - if (mail_scan(stream, "%s", id) != 1 - || mail_print(stream, "%d", flags) != 0) + if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, id, + ATTR_TYPE_END) != 1 + || attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_END) != 0) msg_fatal("unable to contact the %s service", MAIL_SERVICE_CLEANUP); /* @@ -158,8 +160,8 @@ static void post_mail_init(VSTREAM *stream, const char *sender, * Do the Received: and Date: header lines. This allows us to shave a few * cycles by using the expensive date conversion result for both. */ - post_mail_fprintf(stream, "Received: by %s (%s) via %s", - var_myhostname, var_mail_name, via); + post_mail_fprintf(stream, "Received: by %s (%s)", + var_myhostname, var_mail_name); post_mail_fprintf(stream, "\tid %s; %s", vstring_str(id), date); post_mail_fprintf(stream, "Date: %s", date); vstring_free(id); @@ -167,26 +169,25 @@ static void post_mail_init(VSTREAM *stream, const char *sender, /* post_mail_fopen - prepare for posting a message */ -VSTREAM *post_mail_fopen(const char *sender, const char *recipient, - int flags, const char *via) +VSTREAM *post_mail_fopen(const char *sender, const char *recipient, int flags) { VSTREAM *stream; - stream = mail_connect_wait(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP); - post_mail_init(stream, sender, recipient, flags, via); + stream = mail_connect_wait(MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP); + post_mail_init(stream, sender, recipient, flags); return (stream); } /* post_mail_fopen_nowait - prepare for posting a message */ VSTREAM *post_mail_fopen_nowait(const char *sender, const char *recipient, - int flags, const char *via) + int flags) { VSTREAM *stream; - if ((stream = mail_connect(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP, + if ((stream = mail_connect(MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP, BLOCKING)) != 0) - post_mail_init(stream, sender, recipient, flags, via); + post_mail_init(stream, sender, recipient, flags); return (stream); } @@ -235,7 +236,10 @@ int post_mail_fclose(VSTREAM *cleanup) } else { rec_fputs(cleanup, REC_TYPE_XTRA, ""); rec_fputs(cleanup, REC_TYPE_END, ""); - if (vstream_fflush(cleanup) || mail_scan(cleanup, "%d", &status) != 1) + if (vstream_fflush(cleanup) + || attr_scan(cleanup, ATTR_FLAG_MISSING, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_END) != 1) status = CLEANUP_STAT_WRITE; } (void) vstream_fclose(cleanup); diff --git a/gnu/dist/postfix/src/global/post_mail.h b/gnu/dist/postfix/src/global/post_mail.h index 8fa5a80bf3b..264afc77bff 100644 --- a/gnu/dist/postfix/src/global/post_mail.h +++ b/gnu/dist/postfix/src/global/post_mail.h @@ -24,9 +24,8 @@ /* * External interface. */ -extern VSTREAM *post_mail_fopen(const char *, const char *, int, const char *); -extern VSTREAM *post_mail_fopen_nowait(const char *, const char *, - int, const char *); +extern VSTREAM *post_mail_fopen(const char *, const char *, int); +extern VSTREAM *post_mail_fopen_nowait(const char *, const char *, int); extern int PRINTFLIKE(2, 3) post_mail_fprintf(VSTREAM *, const char *,...); extern int post_mail_fputs(VSTREAM *, const char *); extern int post_mail_buffer(VSTREAM *, const char *, int); diff --git a/gnu/dist/postfix/src/global/qmqp_proto.h b/gnu/dist/postfix/src/global/qmqp_proto.h new file mode 100644 index 00000000000..8ad9e2aadaf --- /dev/null +++ b/gnu/dist/postfix/src/global/qmqp_proto.h @@ -0,0 +1,27 @@ +/*++ +/* NAME +/* qmqpd 3h +/* SUMMARY +/* QMQP protocol +/* SYNOPSIS +/* include <qmqpd_proto.h> +/* DESCRIPTION +/* .nf + + /* + * QMQP protocol status codes. + */ +#define QMQP_STAT_OK 'K' /* success */ +#define QMQP_STAT_RETRY 'Z' /* recoverable error */ +#define QMQP_STAT_HARD 'D' /* unrecoverable error */ + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/gnu/dist/postfix/src/global/rec_type.c b/gnu/dist/postfix/src/global/rec_type.c index c86a461aa14..d1e630b3709 100644 --- a/gnu/dist/postfix/src/global/rec_type.c +++ b/gnu/dist/postfix/src/global/rec_type.c @@ -48,7 +48,9 @@ REC_TYPE_NAME rec_type_names[] = { REC_TYPE_FROM, "sender", REC_TYPE_DONE, "done", REC_TYPE_RCPT, "recipient", + REC_TYPE_ORCP, "original recipient", REC_TYPE_WARN, "warning_message_time", + REC_TYPE_ATTR, "named attribute", REC_TYPE_MESG, "message_content", REC_TYPE_CONT, "unterminated", REC_TYPE_NORM, "normal_data", @@ -56,6 +58,7 @@ REC_TYPE_NAME rec_type_names[] = { REC_TYPE_RRTO, "return_receipt", REC_TYPE_ERTO, "errors_to", REC_TYPE_PRIO, "priority", + REC_TYPE_VERP, "verp_delimiters", REC_TYPE_END, "message_end", 0, 0, }; diff --git a/gnu/dist/postfix/src/global/rec_type.h b/gnu/dist/postfix/src/global/rec_type.h index 4412ee37da4..f1caaa466fe 100644 --- a/gnu/dist/postfix/src/global/rec_type.h +++ b/gnu/dist/postfix/src/global/rec_type.h @@ -33,7 +33,9 @@ #define REC_TYPE_FROM 'S' /* sender, required */ #define REC_TYPE_DONE 'D' /* delivered recipient, optional */ #define REC_TYPE_RCPT 'R' /* todo recipient, optional */ +#define REC_TYPE_ORCP 'O' /* original recipient, optional */ #define REC_TYPE_WARN 'W' /* warning message time */ +#define REC_TYPE_ATTR 'A' /* named attribute for extensions */ #define REC_TYPE_MESG 'M' /* start message records */ @@ -45,6 +47,7 @@ #define REC_TYPE_RRTO 'r' /* return-receipt, from headers */ #define REC_TYPE_ERTO 'e' /* errors-to, from headers */ #define REC_TYPE_PRIO 'P' /* priority */ +#define REC_TYPE_VERP 'V' /* VERP delimiters */ #define REC_TYPE_END 'E' /* terminator, required */ @@ -53,9 +56,9 @@ * record groups. The first member in each set is the record type that * indicates the end of that record group. */ -#define REC_TYPE_ENVELOPE "MCTFILSDRW" +#define REC_TYPE_ENVELOPE "MCTFILSDROWVA" #define REC_TYPE_CONTENT "XLN" -#define REC_TYPE_EXTRACT "EDRPre" +#define REC_TYPE_EXTRACT "EDROPre" /* NOT A */ #define REC_TYPE_NOEXTRACT "E" /* diff --git a/gnu/dist/postfix/src/global/recdump.c b/gnu/dist/postfix/src/global/recdump.c index a02d1755e5d..74ff7ea02e0 100644 --- a/gnu/dist/postfix/src/global/recdump.c +++ b/gnu/dist/postfix/src/global/recdump.c @@ -44,7 +44,8 @@ int main(int unused_argc, char **argv) msg_vstream_init(argv[0], VSTREAM_OUT); while (offset = vstream_ftell(VSTREAM_IN), - (type = rec_get(VSTREAM_IN, buf, 0)) > 0) { + ((type = rec_get(VSTREAM_IN, buf, 0)) != REC_TYPE_EOF + && type != REC_TYPE_ERROR)) { vstream_fprintf(VSTREAM_OUT, "%15s|%4ld|%3d|%s\n", rec_type_name(type), offset, VSTRING_LEN(buf), vstring_str(buf)); diff --git a/gnu/dist/postfix/src/global/resolve_clnt.c b/gnu/dist/postfix/src/global/resolve_clnt.c index ff27504a627..56382876541 100644 --- a/gnu/dist/postfix/src/global/resolve_clnt.c +++ b/gnu/dist/postfix/src/global/resolve_clnt.c @@ -156,13 +156,20 @@ void resolve_clnt_query(const char *addr, RESOLVE_REPLY *reply) for (;;) { stream = clnt_stream_access(rewrite_clnt_stream); - if (mail_print(stream, "%s %s", RESOLVE_ADDR, addr) + if (attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_REQ, RESOLVE_ADDR, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, + ATTR_TYPE_END) || vstream_fflush(stream)) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("%s: bad write: %m", myname); - } else if (mail_scan(stream, "%s %s %s %d", - reply->transport, reply->nexthop, - reply->recipient, &reply->flags) != 4) { + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, reply->transport, + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, reply->nexthop, + + ATTR_TYPE_STR, MAIL_ATTR_RECIP, reply->recipient, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, &reply->flags, + ATTR_TYPE_END) != 4) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("%s: bad read: %m", myname); } else { diff --git a/gnu/dist/postfix/src/global/rewrite_clnt.c b/gnu/dist/postfix/src/global/rewrite_clnt.c index ba9049ad3b6..886dc4922eb 100644 --- a/gnu/dist/postfix/src/global/rewrite_clnt.c +++ b/gnu/dist/postfix/src/global/rewrite_clnt.c @@ -123,11 +123,17 @@ VSTRING *rewrite_clnt(const char *rule, const char *addr, VSTRING *result) for (;;) { stream = clnt_stream_access(rewrite_clnt_stream); - if (mail_print(stream, "%s %s %s", REWRITE_ADDR, rule, addr), + if (attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_REQ, REWRITE_ADDR, + ATTR_TYPE_STR, MAIL_ATTR_RULE, rule, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, addr, + ATTR_TYPE_END), vstream_fflush(stream)) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("%s: bad write: %m", myname); - } else if (mail_scan(stream, "%s", result) != 1) { + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, result, + ATTR_TYPE_END) != 1) { if (msg_verbose || (errno != EPIPE && errno != ENOENT)) msg_warn("%s: bad read: %m", myname); } else { diff --git a/gnu/dist/postfix/src/global/smtp_stream.c b/gnu/dist/postfix/src/global/smtp_stream.c index 606ffb65fc0..751e92aa6ae 100644 --- a/gnu/dist/postfix/src/global/smtp_stream.c +++ b/gnu/dist/postfix/src/global/smtp_stream.c @@ -200,6 +200,9 @@ int smtp_get(VSTRING *vp, VSTREAM *stream, int bound) * Allow for partial long lines (we will read the remainder later) and * allow for lines ending in bare LF. The idea is to be liberal in what * we accept, strict in what we send. + * + * XXX 2821: Section 4.1.1.4 says that an SMTP server must not recognize + * bare LF as record terminator. */ smtp_timeout_reset(stream); last_char = (bound == 0 ? vstring_get(vp, stream) : @@ -224,11 +227,13 @@ int smtp_get(VSTRING *vp, VSTREAM *stream, int bound) /* * Strip off the record terminator: either CRLF or just bare LF. + * + * XXX RFC 2821 disallows sending bare CR everywhere. We remove bare CR + * if received before CRLF, and leave it alone otherwise. */ case '\n': - if (VSTRING_LEN(vp) > 1 && vstring_end(vp)[-2] == '\r') - vstring_truncate(vp, VSTRING_LEN(vp) - 2); - else + vstring_truncate(vp, VSTRING_LEN(vp) - 1); + while (VSTRING_LEN(vp) > 0 && vstring_end(vp)[-1] == '\r') vstring_truncate(vp, VSTRING_LEN(vp) - 1); VSTRING_TERMINATE(vp); diff --git a/gnu/dist/postfix/src/global/string_list.c b/gnu/dist/postfix/src/global/string_list.c index bcb457e77a7..522ff26a1df 100644 --- a/gnu/dist/postfix/src/global/string_list.c +++ b/gnu/dist/postfix/src/global/string_list.c @@ -6,7 +6,8 @@ /* SYNOPSIS /* #include <string_list.h> /* -/* STRING_LIST *string_list_init(pattern_list) +/* STRING_LIST *string_list_init(flags, pattern_list) +/* int flags; /* const char *pattern_list; /* /* int string_list_match(list, name) @@ -16,6 +17,8 @@ /* void string_list_free(list) /* STRING_LIST *list; /* DESCRIPTION +/* This is a convenience wrapper around the match_list module. +/* /* This module implements tests for list membership of a string. /* /* Patterns are separated by whitespace and/or commas. A pattern @@ -28,8 +31,8 @@ /* In order to reverse the result, precede a non-file name pattern /* with an exclamation point (!). /* -/* string_list_init() performs initializations. The argument is a -/* list of string patterns. +/* string_list_init() performs initializations. The flags argument +/* is ignored; pattern_list specifies a list of string patterns. /* /* string_list_match() matches the specified string against the /* compiled pattern list. @@ -58,33 +61,11 @@ /* Utility library. */ #include <match_list.h> -#include <match_ops.h> /* Global library. */ #include "string_list.h" -/* string_list_init - initialize string list */ - -STRING_LIST *string_list_init(const char *patterns) -{ - return (match_list_init(patterns, 1, match_string)); -} - -/* string_list_match - match string against list */ - -int string_list_match(STRING_LIST * list, const char *string) -{ - return (match_list_match(list, string)); -} - -/* string_list_free - release storage */ - -void string_list_free(STRING_LIST * list) -{ - match_list_free(list); -} - #ifdef TEST #include <msg.h> @@ -117,7 +98,7 @@ main(int argc, char **argv) } if (argc != optind + 2) usage(argv[0]); - list = string_list_init(argv[optind]); + list = string_list_init(MATCH_FLAG_NONE, argv[optind]); string = argv[optind + 1]; vstream_printf("%s: %s\n", string, string_list_match(list, string) ? "YES" : "NO"); diff --git a/gnu/dist/postfix/src/global/string_list.h b/gnu/dist/postfix/src/global/string_list.h index 946ecf1d296..daa9541d2f5 100644 --- a/gnu/dist/postfix/src/global/string_list.h +++ b/gnu/dist/postfix/src/global/string_list.h @@ -12,13 +12,19 @@ /* .nf /* + * Utility library. + */ +#include <match_list.h> +#include <match_ops.h> + + /* * External interface. */ -typedef struct MATCH_LIST STRING_LIST; +#define STRING_LIST MATCH_LIST -extern STRING_LIST *string_list_init(const char *); -extern int string_list_match(STRING_LIST *, const char *); -extern void string_list_free(STRING_LIST *); +#define string_list_init(f, p) match_list_init((f), (p), 1, match_string) +#define string_list_match match_list_match +#define string_list_free match_list_free /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/global/tok822.h b/gnu/dist/postfix/src/global/tok822.h index c0dd10f5c58..316dfb19821 100644 --- a/gnu/dist/postfix/src/global/tok822.h +++ b/gnu/dist/postfix/src/global/tok822.h @@ -45,8 +45,7 @@ typedef struct TOK822 { #define TOK822_DOMLIT 259 /* stuff between [] not nesting */ #define TOK822_ADDR 260 /* actually a token group */ #define TOK822_STARTGRP 261 /* start of named group */ -#define TOK822_COMMENT_TEXT 262 /* comment text */ -#define TOK822_MAXTOK 262 +#define TOK822_MAXTOK 261 /* * tok822_node.c diff --git a/gnu/dist/postfix/src/global/tok822_node.c b/gnu/dist/postfix/src/global/tok822_node.c index 797deb3424e..d39bf15b94f 100644 --- a/gnu/dist/postfix/src/global/tok822_node.c +++ b/gnu/dist/postfix/src/global/tok822_node.c @@ -57,7 +57,7 @@ TOK822 *tok822_alloc(int type, const char *strval) TOK822 *tp; #define CONTAINER_TOKEN(x) \ - ((x) == TOK822_ADDR || (x) == TOK822_COMMENT || (x) == TOK822_STARTGRP) + ((x) == TOK822_ADDR || (x) == TOK822_STARTGRP) tp = (TOK822 *) mymalloc(sizeof(*tp)); tp->type = type; diff --git a/gnu/dist/postfix/src/global/tok822_parse.ref b/gnu/dist/postfix/src/global/tok822_parse.ref index dc35bff57b7..169957ac40e 100644 --- a/gnu/dist/postfix/src/global/tok822_parse.ref +++ b/gnu/dist/postfix/src/global/tok822_parse.ref @@ -86,8 +86,7 @@ Parse tree: OP "," address atom "venema" - comment - text ""wietse " + comment "("wietse )" OP "," address quoted string ")" @@ -135,11 +134,7 @@ Parse tree: OP "." atom "org" OP ")" - comment - text " " - comment - text ""wietse " - text " venema"" + comment "( ("wietse ) venema")" Internalized: wietse venema@porcupine.org) ( ("wietse ) venema") @@ -272,9 +267,8 @@ Parse tree: atom "wietse" OP "@" atom "foo" - comment - text "wietse - venema" + comment "(wietse + venema)" Internalized: wietse@foo (wietse diff --git a/gnu/dist/postfix/src/global/verp_sender.c b/gnu/dist/postfix/src/global/verp_sender.c new file mode 100644 index 00000000000..b7c0f806e06 --- /dev/null +++ b/gnu/dist/postfix/src/global/verp_sender.c @@ -0,0 +1,106 @@ +/*++ +/* NAME +/* verp_sender 3 +/* SUMMARY +/* quote local part of mailbox +/* SYNOPSIS +/* #include <verp_sender.h> +/* +/* VSTRING *verp_sender(dst, delims, sender, recipient) +/* VSTRING *dst; +/* const char *delims; +/* const char *sender; +/* const char *recipient; +/* +/* const char *verp_delims_verify(delims) +/* const char *delims; +/* DESCRIPTION +/* verp_sender() encodes the recipient address in the sender +/* address, using the specified delimiters. For example, +/* with delims +=, sender \fIprefix@origin\fR, and +/* recipient \fIuser@domain\fR the result is +/* \fIprefix+user=domain@origin\fR. +/* +/* verp_delims_verify() determines if the specified VERP delimiters +/* have reasonable values. What is reasonable is configured with +/* the verp_delimiter_filter configuration parameter. The result +/* is null in case of success, a description of the problem in +/* case of error. +/* +/* Arguments: +/* .IP dst +/* The result. The buffer is null terminated. +/* .IP delims +/* VERP formatting characters. +/* .IP sender +/* Sender envelope address. +/* .IP recipient +/* Recipient envelope address. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> + +/* Utility library. */ + +#include <vstring.h> + +/* Global library. */ + +#include <mail_params.h> +#include <verp_sender.h> + +/* verp_sender - encode recipient into envelope sender address */ + +VSTRING *verp_sender(VSTRING *buf, const char *delimiters, + const char *sender, const char *recipient) +{ + int send_local_len; + int rcpt_local_len; + const char *cp; + + /* + * Change prefix@origin into prefix+user=domain@origin. + */ + send_local_len = ((cp = strrchr(sender, '@')) ? + cp - sender : strlen(sender)); + rcpt_local_len = ((cp = strrchr(recipient, '@')) ? + cp - recipient : strlen(recipient)); + vstring_strncpy(buf, sender, send_local_len); + VSTRING_ADDCH(buf, delimiters[0] & 0xff); + vstring_strncat(buf, recipient, rcpt_local_len); + if (recipient[rcpt_local_len] && recipient[rcpt_local_len + 1]) { + VSTRING_ADDCH(buf, delimiters[1] & 0xff); + vstring_strcat(buf, recipient + rcpt_local_len + 1); + } + if (sender[send_local_len] && sender[send_local_len + 1]) { + VSTRING_ADDCH(buf, '@'); + vstring_strcat(buf, sender + send_local_len + 1); + } + VSTRING_TERMINATE(buf); + return (buf); +} + +/* verp_delims_verify - sanitize VERP delimiters */ + +const char *verp_delims_verify(const char *delims) +{ + if (strlen(delims) != 2) + return ("bad VERP delimiter character count"); + if (strchr(var_verp_filter, delims[0]) == 0) + return ("bad first VERP delimiter character"); + if (strchr(var_verp_filter, delims[1]) == 0) + return ("bad second VERP delimiter character"); + return (0); +} diff --git a/gnu/dist/postfix/src/global/verp_sender.h b/gnu/dist/postfix/src/global/verp_sender.h new file mode 100644 index 00000000000..61641ac8a14 --- /dev/null +++ b/gnu/dist/postfix/src/global/verp_sender.h @@ -0,0 +1,36 @@ +#ifndef _VERP_SENDER_H_INCLUDED_ +#define _VERP_SENDER_H_INCLUDED_ + +/*++ +/* NAME +/* verp_sender 3h +/* SUMMARY +/* encode recipient into sender, VERP style +/* SYNOPSIS +/* #include "verp_sender.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *verp_sender(VSTRING *, const char *, const char *, const char *); +extern const char *verp_delims_verify(const char *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/lmtp/Makefile.in b/gnu/dist/postfix/src/lmtp/Makefile.in index 9821cf6af48..cb8f5637fd9 100644 --- a/gnu/dist/postfix/src/lmtp/Makefile.in +++ b/gnu/dist/postfix/src/lmtp/Makefile.in @@ -132,6 +132,7 @@ lmtp_connect.o: ../../include/timed_connect.h lmtp_connect.o: ../../include/stringops.h lmtp_connect.o: ../../include/mail_params.h lmtp_connect.o: ../../include/mail_proto.h +lmtp_connect.o: ../../include/attr.h lmtp_connect.o: ../../include/dns.h lmtp_connect.o: lmtp.h lmtp_connect.o: ../../include/argv.h @@ -175,6 +176,8 @@ lmtp_sasl_glue.o: ../../include/split_at.h lmtp_sasl_glue.o: ../../include/name_mask.h lmtp_sasl_glue.o: ../../include/mail_params.h lmtp_sasl_glue.o: ../../include/string_list.h +lmtp_sasl_glue.o: ../../include/match_list.h +lmtp_sasl_glue.o: ../../include/match_ops.h lmtp_sasl_glue.o: ../../include/maps.h lmtp_sasl_glue.o: ../../include/dict.h lmtp_sasl_glue.o: ../../include/vstream.h diff --git a/gnu/dist/postfix/src/lmtp/lmtp.c b/gnu/dist/postfix/src/lmtp/lmtp.c index 8a1560c5350..17a5e9c598d 100644 --- a/gnu/dist/postfix/src/lmtp/lmtp.c +++ b/gnu/dist/postfix/src/lmtp/lmtp.c @@ -50,6 +50,7 @@ /* RFC 2033 (LMTP protocol) /* RFC 2197 (Pipelining) /* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) /* DIAGNOSTICS /* Problems and transactions are logged to \fBsyslogd\fR(8). /* Corrupted message files are marked so that the queue manager can @@ -87,7 +88,7 @@ /* The TCP port to be used when connecting to a LMTP server. Used as /* backup if the \fBlmtp\fR service is not found in \fBservices\fR(4). /* .SH "Authentication controls" -/* .IP \fBlmtp_enable_sasl_auth\fR +/* .IP \fBlmtp_sasl_auth_enable\fR /* Enable per-session authentication as per RFC 2554 (SASL). /* By default, Postfix is built without SASL support. /* .IP \fBlmtp_sasl_password_maps\fR @@ -226,8 +227,6 @@ #include <string.h> #include <fcntl.h> #include <dict.h> -#include <pwd.h> -#include <grp.h> /* Utility library. */ @@ -269,8 +268,6 @@ int var_lmtp_data0_tmout; int var_lmtp_data1_tmout; int var_lmtp_data2_tmout; int var_lmtp_quit_tmout; -char *var_debug_peer_list; -int var_debug_peer_level; int var_lmtp_cache_conn; int var_lmtp_skip_quit_resp; char *var_notify_classes; @@ -512,7 +509,6 @@ static void pre_accept(char *unused_name, char **unused_argv) int main(int argc, char **argv) { static CONFIG_STR_TABLE str_table[] = { - VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0, VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, VAR_ERROR_RCPT, DEF_ERROR_RCPT, &var_error_rcpt, 1, 0, VAR_LMTP_SASL_PASSWD, DEF_LMTP_SASL_PASSWD, &var_lmtp_sasl_passwd, 0, 0, @@ -521,7 +517,6 @@ int main(int argc, char **argv) }; static CONFIG_INT_TABLE int_table[] = { VAR_LMTP_TCP_PORT, DEF_LMTP_TCP_PORT, &var_lmtp_tcp_port, 0, 0, - VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0, 0, }; static CONFIG_TIME_TABLE time_table[] = { diff --git a/gnu/dist/postfix/src/lmtp/lmtp.h b/gnu/dist/postfix/src/lmtp/lmtp.h index 9574b648ac0..419cb38d5b0 100644 --- a/gnu/dist/postfix/src/lmtp/lmtp.h +++ b/gnu/dist/postfix/src/lmtp/lmtp.h @@ -61,7 +61,7 @@ typedef struct LMTP_STATE { #define LMTP_FEATURE_8BITMIME (1<<1) #define LMTP_FEATURE_PIPELINING (1<<2) #define LMTP_FEATURE_SIZE (1<<3) -#define SMTP_FEATURE_AUTH (1<<5) +#define LMTP_FEATURE_AUTH (1<<5) /* * lmtp.c diff --git a/gnu/dist/postfix/src/lmtp/lmtp_chat.c b/gnu/dist/postfix/src/lmtp/lmtp_chat.c index 19b37cc03c4..40f5947165a 100644 --- a/gnu/dist/postfix/src/lmtp/lmtp_chat.c +++ b/gnu/dist/postfix/src/lmtp/lmtp_chat.c @@ -268,7 +268,7 @@ void lmtp_chat_notify(LMTP_STATE *state) notice = post_mail_fopen_nowait(mail_addr_double_bounce(), var_error_rcpt, - NULL_CLEANUP_FLAGS, "NOTICE"); + NULL_CLEANUP_FLAGS); if (notice == 0) { msg_warn("postmaster notify: %m"); return; diff --git a/gnu/dist/postfix/src/lmtp/lmtp_proto.c b/gnu/dist/postfix/src/lmtp/lmtp_proto.c index 1c970dad728..7cf98f77cc6 100644 --- a/gnu/dist/postfix/src/lmtp/lmtp_proto.c +++ b/gnu/dist/postfix/src/lmtp/lmtp_proto.c @@ -218,7 +218,7 @@ int lmtp_lhlo(LMTP_STATE *state) lines = resp->str; (void) mystrtok(&lines, "\n"); while ((words = mystrtok(&lines, "\n")) != 0) { - if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) { + if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { if (strcasecmp(word, "8BITMIME") == 0) state->features |= LMTP_FEATURE_8BITMIME; else if (strcasecmp(word, "PIPELINING") == 0) @@ -235,7 +235,7 @@ int lmtp_lhlo(LMTP_STATE *state) msg_info("server features: 0x%x", state->features); #ifdef USE_SASL_AUTH - if (var_lmtp_sasl_enable && (state->features & SMTP_FEATURE_AUTH)) + if (var_lmtp_sasl_enable && (state->features & LMTP_FEATURE_AUTH)) return (lmtp_sasl_helo_login(state)); #endif @@ -489,9 +489,19 @@ static int lmtp_loop(LMTP_STATE *state, int send_state, int recv_state) * rejected, ignore RCPT TO responses: all recipients are * dead already. When all recipients are rejected the * receiver may apply a course correction. + * + * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply + * must be treated as if the server replied with 452. + * However, this causes "too much mail data" to be + * treated as a recoverable error, which is wrong. I'll + * stick with RFC 821. */ case LMTP_STATE_RCPT: if (!mail_from_rejected) { +#ifdef notdef + if (resp->code == 552) + resp->code = 452; +#endif rcpt = request->rcpt_list.info + recv_rcpt; if (resp->code / 100 == 2) { if (survivors == 0) diff --git a/gnu/dist/postfix/src/lmtp/lmtp_sasl_proto.c b/gnu/dist/postfix/src/lmtp/lmtp_sasl_proto.c index 8eaeafd1489..b8c280c019c 100644 --- a/gnu/dist/postfix/src/lmtp/lmtp_sasl_proto.c +++ b/gnu/dist/postfix/src/lmtp/lmtp_sasl_proto.c @@ -14,14 +14,14 @@ /* LMTP_STATE *state; /* DESCRIPTION /* This module contains random chunks of code that implement -/* the SMTP protocol interface for SASL negotiation. The goal +/* the LMTP protocol interface for SASL negotiation. The goal /* is to reduce clutter in the main LMTP client source code. /* /* lmtp_sasl_helo_auth() processes the AUTH option in the -/* SMTP server's EHLO response. +/* LMTP server's LHLO response. /* /* lmtp_sasl_helo_login() authenticates the LMTP client to the -/* SMTP server, using the authentication mechanism information +/* LMTP server, using the authentication mechanism information /* given by the server. The result is a Postfix delivery status /* code in case of trouble. /* @@ -52,6 +52,10 @@ /* System library. */ #include <sys_defs.h> +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif /* Utility library. */ @@ -79,15 +83,17 @@ void lmtp_sasl_helo_auth(LMTP_STATE *state, const char *words) * then pretend that the server doesn't support SASL authentication. */ if (state->sasl_mechanism_list) { + if (strcasecmp(state->sasl_mechanism_list, words) == 0) + return; myfree(state->sasl_mechanism_list); msg_warn("%s offered AUTH option multiple times", state->session->namaddr); state->sasl_mechanism_list = 0; - state->features &= ~SMTP_FEATURE_AUTH; + state->features &= ~LMTP_FEATURE_AUTH; } if (strlen(words) > 0) { state->sasl_mechanism_list = mystrdup(words); - state->features |= SMTP_FEATURE_AUTH; + state->features |= LMTP_FEATURE_AUTH; } else { msg_warn("%s offered null AUTH mechanism list", state->session->namaddr); diff --git a/gnu/dist/postfix/src/local/Makefile.in b/gnu/dist/postfix/src/local/Makefile.in index ff36cf9bf65..fc6a4ae2d76 100644 --- a/gnu/dist/postfix/src/local/Makefile.in +++ b/gnu/dist/postfix/src/local/Makefile.in @@ -213,6 +213,7 @@ forward.o: ../../include/vstring_vstream.h forward.o: ../../include/iostuff.h forward.o: ../../include/stringops.h forward.o: ../../include/mail_proto.h +forward.o: ../../include/attr.h forward.o: ../../include/cleanup_user.h forward.o: ../../include/sent.h forward.o: ../../include/record.h @@ -334,13 +335,16 @@ mailbox.o: ../../include/deliver_request.h mailbox.o: ../../include/recipient_list.h mailbox.o: ../../include/mail_proto.h mailbox.o: ../../include/iostuff.h +mailbox.o: ../../include/attr.h mailbox.o: ../../include/mbox_open.h mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/maps.h +mailbox.o: ../../include/dict.h +mailbox.o: ../../include/argv.h mailbox.o: local.h mailbox.o: ../../include/tok822.h mailbox.o: ../../include/resolve_clnt.h mailbox.o: ../../include/mbox_conf.h -mailbox.o: ../../include/argv.h mailbox.o: biff_notify.h maildir.o: maildir.c maildir.o: ../../include/sys_defs.h @@ -403,6 +407,7 @@ resolve.o: ../../include/htable.h resolve.o: ../../include/mail_proto.h resolve.o: ../../include/vstream.h resolve.o: ../../include/iostuff.h +resolve.o: ../../include/attr.h resolve.o: ../../include/resolve_clnt.h resolve.o: ../../include/rewrite_clnt.h resolve.o: ../../include/tok822.h @@ -446,6 +451,7 @@ unknown.o: ../../include/mail_params.h unknown.o: ../../include/mail_proto.h unknown.o: ../../include/vstream.h unknown.o: ../../include/iostuff.h +unknown.o: ../../include/attr.h unknown.o: ../../include/bounce.h unknown.o: local.h unknown.o: ../../include/htable.h diff --git a/gnu/dist/postfix/src/local/alias.c b/gnu/dist/postfix/src/local/alias.c index 69e0b4e7991..fe1eb46fd7d 100644 --- a/gnu/dist/postfix/src/local/alias.c +++ b/gnu/dist/postfix/src/local/alias.c @@ -135,7 +135,8 @@ int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, struct mypasswd *alias_pwd; VSTRING *canon_owner; DICT *dict; - const char *owner_rhs; /* owner alias, RHS */ + const char *owner_rhs; /* owner alias, RHS */ + int alias_count; /* * Make verbose logging easier to understand. @@ -171,7 +172,7 @@ int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, if (state.level > 100) { msg_warn("possible alias database loop for %s", name); *statusp = bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), - "possible alias database loop for %s", name); + "possible alias database loop for %s", name); return (YES); } state.msg_attr.exp_from = name; @@ -271,11 +272,19 @@ int deliver_alias(LOCAL_STATE state, USER_ATTR usr_attr, /* * Deliver. */ + alias_count = 0; *statusp = (dict_errno ? defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), "alias database unavailable") : - deliver_token_string(state, usr_attr, expansion, (int *) 0)); + deliver_token_string(state, usr_attr, expansion, &alias_count)); +#if 0 + if (var_ownreq_special + && strncmp("owner-", state.msg_attr.sender, 6) != 0 + && alias_count > 10) + msg_warn("mailing list \"%s\" needs an \"owner-%s\" alias", + name, name); +#endif myfree(expansion); if (owner) myfree(owner); diff --git a/gnu/dist/postfix/src/local/command.c b/gnu/dist/postfix/src/local/command.c index 23fffdf2ff3..8a86ac63a05 100644 --- a/gnu/dist/postfix/src/local/command.c +++ b/gnu/dist/postfix/src/local/command.c @@ -9,7 +9,7 @@ /* int deliver_command(state, usr_attr, command) /* LOCAL_STATE state; /* USER_ATTR exp_attr; -/* char *command; +/* const char *command; /* DESCRIPTION /* deliver_command() runs a command with a message as standard /* input. A limited amount of standard output and standard error @@ -79,7 +79,7 @@ /* deliver_command - deliver to shell command */ -int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, char *command) +int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, const char *command) { char *myname = "deliver_command"; VSTRING *why; @@ -107,16 +107,6 @@ int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, char *command) return (0); /* - * DELIVERY POLICY - * - * Do we permit mail to shell commands? Allow delivery via mailbox_command. - */ - if (command != var_mailbox_command - && (local_cmd_deliver_mask & state.msg_attr.exp_type) == 0) - return (bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), - "mail to command is restricted")); - - /* * DELIVERY RIGHTS * * Choose a default uid and gid when none have been selected (i.e. values @@ -205,6 +195,9 @@ int deliver_command(LOCAL_STATE state, USER_ATTR usr_attr, char *command) BOUNCE_ATTR(state.msg_attr), "%s", vstring_str(why)); break; + case PIPE_STAT_CORRUPT: + deliver_status = DEL_STAT_DEFER; + break; default: msg_panic("%s: bad status %d", myname, cmd_status); /* NOTREACHED */ diff --git a/gnu/dist/postfix/src/local/file.c b/gnu/dist/postfix/src/local/file.c index e3a530ba8a1..96663d3c41e 100644 --- a/gnu/dist/postfix/src/local/file.c +++ b/gnu/dist/postfix/src/local/file.c @@ -86,7 +86,8 @@ int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) struct stat st; MBOX *mp; VSTRING *why; - int status = -1; + int mail_copy_status = MAIL_COPY_STAT_WRITE; + int deliver_status; int copy_flags; /* @@ -157,10 +158,10 @@ int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) vstring_sprintf(why, "destination file is executable"); errno = 0; } else { - status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, - S_ISREG(st.st_mode) ? copy_flags : - (copy_flags & ~MAIL_COPY_TOFILE), - "\n", why); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + S_ISREG(st.st_mode) ? copy_flags : + (copy_flags & ~MAIL_COPY_TOFILE), + "\n", why); } mbox_release(mp); } @@ -169,19 +170,21 @@ int deliver_file(LOCAL_STATE state, USER_ATTR usr_attr, char *path) /* * As the mail system, bounce, defer delivery, or report success. */ - if (status != 0) { - status = (errno == EAGAIN || errno == ENOSPC ? - defer_append : bounce_append) + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + deliver_status = (errno == EAGAIN || errno == ENOSPC || errno == ESTALE ? + defer_append : bounce_append) (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), "cannot append message to destination file %s: %s", path, STR(why)); } else { - sent(SENT_ATTR(state.msg_attr), "%s", path); + deliver_status = sent(SENT_ATTR(state.msg_attr), "%s", path); } /* * Clean up. */ vstring_free(why); - return (status); + return (deliver_status); } diff --git a/gnu/dist/postfix/src/local/forward.c b/gnu/dist/postfix/src/local/forward.c index 91f239348d6..5efbf181341 100644 --- a/gnu/dist/postfix/src/local/forward.c +++ b/gnu/dist/postfix/src/local/forward.c @@ -120,12 +120,18 @@ static FORWARD_INFO *forward_open(char *sender) * Contact the cleanup service and save the new mail queue id. Request * that the cleanup service bounces bad messages to the sender so that we * can avoid the trouble of bounce management. + * + * In case you wonder what kind of bounces, examples are "too many hops", + * "message too large", perhaps some others. The reason not to bounce + * ourselves is that we don't really know who the recipients are. */ - cleanup = mail_connect(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP, BLOCKING); + cleanup = mail_connect(MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP, BLOCKING); if (cleanup == 0) return (0); close_on_exec(vstream_fileno(cleanup), CLOSE_ON_EXEC); - if (mail_scan(cleanup, "%s", buffer) != 1) { + if (attr_scan(cleanup, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buffer, + ATTR_TYPE_END) != 1) { vstream_fclose(cleanup); return (0); } @@ -133,7 +139,9 @@ static FORWARD_INFO *forward_open(char *sender) info->cleanup = cleanup; info->queue_id = mystrdup(vstring_str(buffer)); info->posting_time = time((time_t *) 0); - mail_print(cleanup, "%d", CLEANUP_FLAG_BOUNCE); + attr_print(cleanup, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, CLEANUP_FLAG_BOUNCE, + ATTR_TYPE_END); /* * Send initial message envelope information. For bounces, set the @@ -233,7 +241,9 @@ static int forward_send(FORWARD_INFO *info, DELIVER_ATTR attr, char *delivered) */ if (status == 0) if (vstream_fflush(info->cleanup) - || mail_scan(info->cleanup, "%d", &status) != 1) + || attr_scan(info->cleanup, ATTR_FLAG_MISSING, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_END) != 1) status = 1; /* diff --git a/gnu/dist/postfix/src/local/local.c b/gnu/dist/postfix/src/local/local.c index 931b2c98f3d..dc13ca16677 100644 --- a/gnu/dist/postfix/src/local/local.c +++ b/gnu/dist/postfix/src/local/local.c @@ -84,7 +84,8 @@ /* The default per-user mailbox is a file in the UNIX mail spool /* directory (\fB/var/mail/\fIuser\fR or \fB/var/spool/mail/\fIuser\fR); /* the location can be specified with the \fBmail_spool_directory\fR -/* configuration parameter. +/* configuration parameter. Specify a name ending in \fB/\fR for +/* \fBqmail\fR-compatible \fBmaildir\fR delivery. /* /* Alternatively, the per-user mailbox can be a file in the user's home /* directory with a name specified via the \fBhome_mailbox\fR @@ -177,13 +178,16 @@ /* EXTERNAL FILE DELIVERY /* .ad /* .fi +/* The delivery format depends on the destination filename syntax. +/* The default is to use UNIX-style mailbox format. Specify a name +/* ending in \fB/\fR for \fBqmail\fR-compatible \fBmaildir\fR delivery. +/* /* The \fBallow_mail_to_files\fR configuration parameter restricts /* delivery to external files. The default setting (\fBalias, /* forward\fR) forbids file destinations in \fB:include:\fR files. -/* Specify a pathname ending in \fB/\fR for \fBqmail\fR-compatible -/* \fBmaildir\fR delivery. /* -/* The \fBlocal\fR daemon prepends a "\fBFrom \fIsender time_stamp\fR" +/* In the case of UNIX-style mailbox delivery, +/* the \fBlocal\fR daemon prepends a "\fBFrom \fIsender time_stamp\fR" /* envelope header to each message, prepends an /* optional \fBDelivered-To:\fR /* header with the recipient envelope address, prepends a \fB>\fR @@ -303,10 +307,14 @@ /* .IP \fBmail_spool_directory\fR /* Directory with UNIX-style mailboxes. The default pathname is system /* dependent. +/* Specify a path ending in \fB/\fR for maildir-style delivery. /* .IP \fBmailbox_command\fR /* External command to use for mailbox delivery. The command executes /* with the recipient privileges (exception: root). The string is subject /* to $name expansions. +/* .IP \fBmailbox_command_maps\fR +/* Lookup tables with per-recipient external commands to use for mailbox +/* delivery. Behavior is as with \fBmailbox_command\fR. /* .IP \fBmailbox_transport\fR /* Message transport to use for mailbox delivery to all local /* recipients, whether or not they are found in the UNIX passwd database. @@ -446,6 +454,7 @@ int var_dup_filter_limit; int var_command_maxtime; char *var_home_mailbox; char *var_mailbox_command; +char *var_mailbox_cmd_maps; char *var_rcpt_fdelim; char *var_local_cmd_shell; char *var_luser_relay; @@ -695,6 +704,7 @@ int main(int argc, char **argv) static CONFIG_RAW_TABLE raw_table[] = { VAR_FORWARD_PATH, DEF_FORWARD_PATH, &var_forward_path, 0, 0, VAR_MAILBOX_COMMAND, DEF_MAILBOX_COMMAND, &var_mailbox_command, 0, 0, + VAR_MAILBOX_CMD_MAPS, DEF_MAILBOX_CMD_MAPS, &var_mailbox_cmd_maps, 0, 0, VAR_LUSER_RELAY, DEF_LUSER_RELAY, &var_luser_relay, 0, 0, 0, }; diff --git a/gnu/dist/postfix/src/local/local.h b/gnu/dist/postfix/src/local/local.h index 4843179e0d5..302ea541973 100644 --- a/gnu/dist/postfix/src/local/local.h +++ b/gnu/dist/postfix/src/local/local.h @@ -152,7 +152,7 @@ extern int deliver_resolve_addr(LOCAL_STATE, USER_ATTR, char *); * "leaf" nodes of the delivery graph. */ extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *); -extern int deliver_command(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_command(LOCAL_STATE, USER_ATTR, const char *); extern int deliver_file(LOCAL_STATE, USER_ATTR, char *); extern int deliver_indirect(LOCAL_STATE); extern int deliver_maildir(LOCAL_STATE, USER_ATTR, char *); diff --git a/gnu/dist/postfix/src/local/mailbox.c b/gnu/dist/postfix/src/local/mailbox.c index 854590c111a..45d24d8d234 100644 --- a/gnu/dist/postfix/src/local/mailbox.c +++ b/gnu/dist/postfix/src/local/mailbox.c @@ -72,6 +72,7 @@ #include <mail_params.h> #include <deliver_pass.h> #include <mbox_open.h> +#include <maps.h> #ifndef EDQUOT #define EDQUOT EFBIG @@ -94,7 +95,8 @@ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) char *mailbox; VSTRING *why; MBOX *mp; - int status; + int mail_copy_status; + int deliver_status; int copy_flags; VSTRING *biff; long end; @@ -118,7 +120,7 @@ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.recipient; - status = -1; + mail_copy_status = MAIL_COPY_STAT_WRITE; why = vstring_alloc(100); if (*var_home_mailbox) { spool_dir = 0; @@ -189,8 +191,8 @@ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) errno = 0; } else { end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); - status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, - copy_flags, "\n", why); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); } if (spool_uid != usr_attr.uid || spool_gid != usr_attr.gid) set_eugid(spool_uid, spool_gid); @@ -201,14 +203,16 @@ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) /* * As the mail system, bounce, defer delivery, or report success. */ - if (status != 0) { - status = (errno == EAGAIN || errno == ENOSPC ? - defer_append : bounce_append) + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + deliver_status = (errno == EAGAIN || errno == ENOSPC || errno == ESTALE ? + defer_append : bounce_append) (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), "cannot access mailbox %s for user %s. %s", mailbox, state.msg_attr.user, vstring_str(why)); } else { - sent(SENT_ATTR(state.msg_attr), "mailbox"); + deliver_status = sent(SENT_ATTR(state.msg_attr), "mailbox"); if (var_biff) { biff = vstring_alloc(100); vstring_sprintf(biff, "%s@%ld", usr_attr.logname, (long) end); @@ -222,7 +226,7 @@ static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) */ myfree(mailbox); vstring_free(why); - return (status); + return (deliver_status); } /* deliver_mailbox - deliver to recipient mailbox */ @@ -233,6 +237,8 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) int status; struct mypasswd *mbox_pwd; char *path; + static MAPS *cmd_maps; + const char *map_command; /* * Make verbose logging easier to understand. @@ -276,16 +282,29 @@ int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) SET_USER_ATTR(usr_attr, mbox_pwd, state.level); /* - * Deliver to mailbox or to external command. + * Deliver to mailbox, maildir or to external command. */ #define LAST_CHAR(s) (s[strlen(s) - 1]) - if (*var_mailbox_command) + if (*var_mailbox_cmd_maps && cmd_maps == 0) + cmd_maps = maps_create(VAR_MAILBOX_CMD_MAPS, var_mailbox_cmd_maps, + DICT_FLAG_LOCK); + + if (*var_mailbox_cmd_maps + && (map_command = maps_find(cmd_maps, state.msg_attr.user, + DICT_FLAG_FIXED)) != 0) { + status = deliver_command(state, usr_attr, map_command); + } else if (*var_mailbox_command) { status = deliver_command(state, usr_attr, var_mailbox_command); - else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { + } else if (*var_home_mailbox && LAST_CHAR(var_home_mailbox) == '/') { path = concatenate(usr_attr.home, "/", var_home_mailbox, (char *) 0); status = deliver_maildir(state, usr_attr, path); myfree(path); + } else if (*var_mail_spool_dir && LAST_CHAR(var_mail_spool_dir) == '/') { + path = concatenate(var_mail_spool_dir, state.msg_attr.user, + "/", (char *) 0); + status = deliver_maildir(state, usr_attr, path); + myfree(path); } else status = deliver_mailbox_file(state, usr_attr); diff --git a/gnu/dist/postfix/src/local/maildir.c b/gnu/dist/postfix/src/local/maildir.c index e9bd0de85c7..b2ccef3cea5 100644 --- a/gnu/dist/postfix/src/local/maildir.c +++ b/gnu/dist/postfix/src/local/maildir.c @@ -80,7 +80,8 @@ int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) VSTRING *why; VSTRING *buf; VSTREAM *dst; - int status; + int mail_copy_status; + int deliver_status; int copy_flags; static int count; @@ -98,7 +99,7 @@ int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); state.msg_attr.delivered = state.msg_attr.recipient; - status = -1; + mail_copy_status = MAIL_COPY_STAT_WRITE; buf = vstring_alloc(100); why = vstring_alloc(100); @@ -137,14 +138,14 @@ int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { vstring_sprintf(why, "create %s: %m", tmpfile); } else { - if (mail_copy(COPY_ATTR(state.msg_attr), dst, copy_flags, "\n", why) == 0) { + if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), + dst, copy_flags, "\n", why)) == 0) { if (sane_link(tmpfile, newfile) < 0 && (errno != ENOENT || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 || sane_link(tmpfile, newfile) < 0)) { vstring_sprintf(why, "link to %s: %m", newfile); - } else { - status = 0; + mail_copy_status = MAIL_COPY_STAT_WRITE; } } if (unlink(tmpfile) < 0) @@ -152,12 +153,19 @@ int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) } set_eugid(var_owner_uid, var_owner_gid); - if (status) - status = (errno == ENOSPC ? defer_append : bounce_append) + /* + * As the mail system, bounce or defer delivery. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + deliver_status = (errno == ENOSPC || errno == ESTALE ? + defer_append : bounce_append) (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), "maildir delivery failed: %s", vstring_str(why)); - else - status = sent(SENT_ATTR(state.msg_attr), "maildir"); + } else { + deliver_status = sent(SENT_ATTR(state.msg_attr), "maildir"); + } vstring_free(buf); vstring_free(why); myfree(newdir); @@ -165,5 +173,5 @@ int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr, char *path) myfree(curdir); myfree(tmpfile); myfree(newfile); - return (status); + return (deliver_status); } diff --git a/gnu/dist/postfix/src/local/token.c b/gnu/dist/postfix/src/local/token.c index 28803fe4c6c..c427eb7042c 100644 --- a/gnu/dist/postfix/src/local/token.c +++ b/gnu/dist/postfix/src/local/token.c @@ -148,7 +148,12 @@ int deliver_token(LOCAL_STATE state, USER_ATTR usr_attr, TOK822 *addr) } else if (*STR(addr_buf) == '~') { status = deliver_token_home(state, usr_attr, STR(addr_buf)); } else if (*STR(addr_buf) == '|') { - status = deliver_command(state, usr_attr, STR(addr_buf) + 1); + if ((local_cmd_deliver_mask & state.msg_attr.exp_type) == 0) + status = bounce_append(BOUNCE_FLAG_KEEP, + BOUNCE_ATTR(state.msg_attr), + "mail to command is restricted"); + else + status = deliver_command(state, usr_attr, STR(addr_buf) + 1); } else if (strncasecmp(STR(addr_buf), include, sizeof(include) - 1) == 0) { path = STR(addr_buf) + sizeof(include) - 1; status = deliver_include(state, usr_attr, path); diff --git a/gnu/dist/postfix/src/master/Makefile.in b/gnu/dist/postfix/src/master/Makefile.in index 47f3369af07..cbb2894bf3f 100644 --- a/gnu/dist/postfix/src/master/Makefile.in +++ b/gnu/dist/postfix/src/master/Makefile.in @@ -2,12 +2,13 @@ SHELL = /bin/sh SRCS = master.c master_conf.c master_ent.c master_sig.c master_avail.c \ master_spawn.c master_service.c master_status.o master_listen.c \ master_proto.c single_server.c multi_server.c master_vars.c \ - master_wakeup.c + master_wakeup.c master_flow.c mail_flow.c OBJS = master.o master_conf.o master_ent.o master_sig.o master_avail.o \ master_spawn.o master_service.o master_status.o master_listen.o \ - master_vars.o master_wakeup.o -LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o -HDRS = mail_server.h master_proto.h + master_vars.o master_wakeup.o master_flow.o +LIB_OBJ = single_server.o multi_server.o trigger_server.o master_proto.o \ + mail_flow.o +HDRS = mail_server.h master_proto.h mail_flow.h INT_HDR = master.h WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ @@ -78,6 +79,12 @@ depend: $(MAKES) @$(EXPORT) make -f Makefile.in Makefile 1>&2 # do not edit below this line - it is generated by 'make depend' +mail_flow.o: mail_flow.c +mail_flow.o: ../../include/sys_defs.h +mail_flow.o: ../../include/msg.h +mail_flow.o: ../../include/iostuff.h +mail_flow.o: mail_flow.h +mail_flow.o: master_proto.h master.o: master.c master.o: ../../include/sys_defs.h master.o: ../../include/events.h @@ -93,6 +100,7 @@ master.o: ../../include/myflock.h master.o: ../../include/watchdog.h master.o: ../../include/clean_env.h master.o: ../../include/argv.h +master.o: ../../include/safe.h master.o: ../../include/mail_params.h master.o: ../../include/debug_process.h master.o: ../../include/mail_task.h @@ -125,10 +133,17 @@ master_ent.o: ../../include/inet_util.h master_ent.o: ../../include/inet_addr_host.h master_ent.o: ../../include/mail_proto.h master_ent.o: ../../include/iostuff.h +master_ent.o: ../../include/attr.h master_ent.o: ../../include/mail_params.h master_ent.o: ../../include/own_inet_addr.h master_ent.o: master_proto.h master_ent.o: master.h +master_flow.o: master_flow.c +master_flow.o: ../../include/sys_defs.h +master_flow.o: ../../include/msg.h +master_flow.o: ../../include/iostuff.h +master_flow.o: master.h +master_flow.o: master_proto.h master_listen.o: master_listen.c master_listen.o: ../../include/sys_defs.h master_listen.o: ../../include/msg.h @@ -140,6 +155,7 @@ master_listen.o: ../../include/vstring.h master_listen.o: ../../include/vbuf.h master_listen.o: ../../include/inet_addr_list.h master_listen.o: ../../include/set_eugid.h +master_listen.o: ../../include/set_ugid.h master_listen.o: ../../include/mail_params.h master_listen.o: master.h master_proto.o: master_proto.c @@ -188,10 +204,14 @@ master_wakeup.o: ../../include/sys_defs.h master_wakeup.o: ../../include/msg.h master_wakeup.o: ../../include/trigger.h master_wakeup.o: ../../include/events.h +master_wakeup.o: ../../include/set_eugid.h +master_wakeup.o: ../../include/set_ugid.h master_wakeup.o: ../../include/mail_proto.h master_wakeup.o: ../../include/vstream.h master_wakeup.o: ../../include/vbuf.h master_wakeup.o: ../../include/iostuff.h +master_wakeup.o: ../../include/attr.h +master_wakeup.o: ../../include/mail_params.h master_wakeup.o: mail_server.h master_wakeup.o: master.h multi_server.o: multi_server.c @@ -219,6 +239,7 @@ multi_server.o: ../../include/mail_params.h multi_server.o: ../../include/mail_conf.h multi_server.o: ../../include/timed_ipc.h multi_server.o: ../../include/resolve_local.h +multi_server.o: mail_flow.h multi_server.o: master_proto.h multi_server.o: mail_server.h single_server.o: single_server.c @@ -246,6 +267,7 @@ single_server.o: ../../include/debug_process.h single_server.o: ../../include/mail_conf.h single_server.o: ../../include/timed_ipc.h single_server.o: ../../include/resolve_local.h +single_server.o: mail_flow.h single_server.o: master_proto.h single_server.o: mail_server.h trigger_server.o: trigger_server.c @@ -272,5 +294,6 @@ trigger_server.o: ../../include/mail_task.h trigger_server.o: ../../include/debug_process.h trigger_server.o: ../../include/mail_conf.h trigger_server.o: ../../include/resolve_local.h +trigger_server.o: mail_flow.h trigger_server.o: master_proto.h trigger_server.o: mail_server.h diff --git a/gnu/dist/postfix/src/master/mail_flow.c b/gnu/dist/postfix/src/master/mail_flow.c new file mode 100644 index 00000000000..efb59885409 --- /dev/null +++ b/gnu/dist/postfix/src/master/mail_flow.c @@ -0,0 +1,140 @@ +/*++ +/* NAME +/* mail_flow 3 +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include <mail_flow.h> +/* +/* int mail_flow_get(count) +/* int count; +/* +/* void mail_flow_put(count) +/* int count; +/* +/* int mail_flow_count() +/* DESCRIPTION +/* This module implements a simple flow control mechanism that +/* is based on tokens that are consumed by mail receiving processes +/* and that are produced by mail sending processes. +/* +/* mail_flow_get() attempts to read specified number of tokens. The +/* result is > 0 for success, < 0 for failure. In the latter case, +/* the process is expected to slow down a little. +/* +/* mail_flow_put() produces the specified number of tokens. The +/* token producing process is expected to produce new tokens +/* whenever it falls idle and no more tokens are available. +/* +/* mail_flow_count() returns the number of available tokens. +/* BUGS +/* The producer needs to wake up periodically to ensure that +/* tokens are not lost due to leakage. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* Global library. */ + +#include <mail_flow.h> + +/* Master library. */ + +#include <master_proto.h> + +#define BUFFER_SIZE 1024 + +/* mail_flow_get - read N tokens */ + +int mail_flow_get(int len) +{ + char *myname = "mail_flow_get"; + char buf[BUFFER_SIZE]; + struct stat st; + int count; + int n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %d", myname, len); + + /* + * Silence some wild claims. + */ + if (fstat(MASTER_FLOW_WRITE, &st) < 0) + msg_fatal("fstat flow pipe write descriptor: %m"); + + /* + * Read and discard N bytes. XXX AIX read() can return 0 when an open + * pipe is empty. + */ + for (count = len; count > 0; count -= n) + if ((n = read(MASTER_FLOW_READ, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) <= 0) + return (-1); + if (msg_verbose) + msg_info("%s: %d %d", myname, len, len - count); + return (len - count); +} + +/* mail_flow_put - put N tokens */ + +int mail_flow_put(int len) +{ + char *myname = "mail_flow_put"; + char buf[BUFFER_SIZE]; + int count; + int n = 0; + + /* + * Sanity check. + */ + if (len <= 0) + msg_panic("%s: bad length %d", myname, len); + + /* + * Write or discard N bytes. + */ + memset(buf, 0, len > BUFFER_SIZE ? BUFFER_SIZE : len); + + for (count = len; count > 0; count -= n) + if ((n = write(MASTER_FLOW_WRITE, buf, count > BUFFER_SIZE ? + BUFFER_SIZE : count)) < 0) + return (-1); + if (msg_verbose) + msg_info("%s: %d %d", myname, len, len - count); + return (len - count); +} + +/* mail_flow_count - return number of available tokens */ + +int mail_flow_count(void) +{ + char *myname = "mail_flow_count"; + int count; + + if ((count = peekfd(MASTER_FLOW_READ)) < 0) + msg_warn("%s: %m", myname); + return (count); +} diff --git a/gnu/dist/postfix/src/master/mail_flow.h b/gnu/dist/postfix/src/master/mail_flow.h new file mode 100644 index 00000000000..9e058598f5d --- /dev/null +++ b/gnu/dist/postfix/src/master/mail_flow.h @@ -0,0 +1,32 @@ +#ifndef _MAIL_FLOW_H_INCLUDED_ +#define _MAIL_FLOW_H_INCLUDED_ + +/*++ +/* NAME +/* mail_flow 3h +/* SUMMARY +/* global mail flow control +/* SYNOPSIS +/* #include <mail_flow.h> +/* DESCRIPTION +/* .nf + + /* + * Functional interface. + */ +extern int mail_flow_get(int); +extern int mail_flow_put(int); +extern int mail_flow_count(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/master/mail_server.h b/gnu/dist/postfix/src/master/mail_server.h index d03313b1ead..47b8cfeeed2 100644 --- a/gnu/dist/postfix/src/master/mail_server.h +++ b/gnu/dist/postfix/src/master/mail_server.h @@ -28,6 +28,8 @@ #define MAIL_SERVER_EXIT 13 #define MAIL_SERVER_PRE_ACCEPT 14 +#define MAIL_SERVER_IN_FLOW_DELAY 20 + typedef void (*MAIL_SERVER_INIT_FN) (char *, char **); typedef int (*MAIL_SERVER_LOOP_FN) (char *, char **); typedef void (*MAIL_SERVER_EXIT_FN) (char *, char **); diff --git a/gnu/dist/postfix/src/master/master.c b/gnu/dist/postfix/src/master/master.c index 4299424f1ce..337ba7931b5 100644 --- a/gnu/dist/postfix/src/master/master.c +++ b/gnu/dist/postfix/src/master/master.c @@ -154,6 +154,7 @@ #include <watchdog.h> #include <clean_env.h> #include <argv.h> +#include <safe.h> /* Global library. */ @@ -241,6 +242,16 @@ int main(int argc, char **argv) msg_syslog_init(mail_task(var_procname), LOG_PID, LOG_FACILITY); /* + * The mail system must be run by the superuser so it can revoke + * privileges for selected operations. That's right - it takes privileges + * to toss privileges. + */ + if (getuid() != 0) + msg_fatal("the master command is reserved for the superuser"); + if (unsafe() != 0) + msg_fatal("the master command must not run as a set-uid process"); + + /* * If started from a terminal, get rid of any tty association. This also * means that all errors and warnings must go to the syslog daemon. */ @@ -256,8 +267,11 @@ int main(int argc, char **argv) * when a service listens on many ports. In order to do this right we * must change the master-child interface so that descriptors do not need * to have fixed numbers. + * + * In a child we need two descriptors for the flow control pipe, one for + * child->master status updates and at least one for listening. */ - for (n = 0; n < 3; n++) { + for (n = 0; n < 5; n++) { if (close_on_exec(dup(0), CLOSE_ON_EXEC) < 0) msg_fatal("dup(0): %m"); } @@ -349,6 +363,7 @@ int main(int argc, char **argv) */ master_config(); master_sigsetup(); + master_flow_init(); msg_info("daemon started"); /* diff --git a/gnu/dist/postfix/src/master/master_flow.c b/gnu/dist/postfix/src/master/master_flow.c new file mode 100644 index 00000000000..ed9b492d22b --- /dev/null +++ b/gnu/dist/postfix/src/master/master_flow.c @@ -0,0 +1,33 @@ +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <iostuff.h> + +/* Application-specific. */ + +#include <master.h> +#include <master_proto.h> + +int master_flow_pipe[2]; + +/* master_flow_init - initialize the flow control channel */ + +void master_flow_init(void) +{ + char *myname = "master_flow_init"; + + if (pipe(master_flow_pipe) < 0) + msg_fatal("%s: pipe: %m", myname); + + non_blocking(master_flow_pipe[0], NON_BLOCKING); + non_blocking(master_flow_pipe[1], NON_BLOCKING); + + close_on_exec(master_flow_pipe[0], CLOSE_ON_EXEC); + close_on_exec(master_flow_pipe[1], CLOSE_ON_EXEC); +} diff --git a/gnu/dist/postfix/src/master/master_proto.h b/gnu/dist/postfix/src/master/master_proto.h index 2929ed46375..36c30f8d5bb 100644 --- a/gnu/dist/postfix/src/master/master_proto.h +++ b/gnu/dist/postfix/src/master/master_proto.h @@ -32,6 +32,14 @@ typedef struct MASTER_STATUS { extern int master_notify(int, int); /* encapsulate status msg */ /* + * File descriptors inherited from the master process. The flow control pipe + * is read by receive processes and is written to by send processes. If + * receive processes get too far ahead they will pause for a brief moment. + */ +#define MASTER_FLOW_READ 3 +#define MASTER_FLOW_WRITE 4 + + /* * File descriptors inherited from the master process. All processes that * provide a given service share the same status file descriptor, and listen * on the same service socket(s). The kernel decides what process gets the @@ -41,8 +49,8 @@ extern int master_notify(int, int); /* encapsulate status msg */ * actually the lowest-numbered descriptor of a sequence of descriptors to * listen on. */ -#define MASTER_STATUS_FD 3 /* shared channel to parent */ -#define MASTER_LISTEN_FD 4 /* accept connections here */ +#define MASTER_STATUS_FD 5 /* shared channel to parent */ +#define MASTER_LISTEN_FD 6 /* accept connections here */ /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/master/master_spawn.c b/gnu/dist/postfix/src/master/master_spawn.c index 75dcfafbeec..2eaf7bf96b3 100644 --- a/gnu/dist/postfix/src/master/master_spawn.c +++ b/gnu/dist/postfix/src/master/master_spawn.c @@ -175,6 +175,23 @@ void master_spawn(MASTER_SERV *serv) case 0: msg_cleanup((void (*) (void)) 0); /* disable exit handler */ closelog(); /* avoid filedes leak */ + + if (master_flow_pipe[0] <= MASTER_FLOW_READ) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_READ); + if (DUP2(master_flow_pipe[0], MASTER_FLOW_READ) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[0]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[0]); + + if (master_flow_pipe[1] <= MASTER_FLOW_WRITE) + msg_fatal("%s: flow pipe read descriptor <= %d", + myname, MASTER_FLOW_WRITE); + if (DUP2(master_flow_pipe[1], MASTER_FLOW_WRITE) < 0) + msg_fatal("%s: dup2: %m", myname); + if (close(master_flow_pipe[1]) < 0) + msg_fatal("close %d: %m", master_flow_pipe[1]); + close(serv->status_fd[0]); /* status channel */ if (serv->status_fd[1] <= MASTER_STATUS_FD) msg_fatal("%s: status file descriptor collision", myname); diff --git a/gnu/dist/postfix/src/master/master_wakeup.c b/gnu/dist/postfix/src/master/master_wakeup.c index 16cc7be5bcc..d3052e688ff 100644 --- a/gnu/dist/postfix/src/master/master_wakeup.c +++ b/gnu/dist/postfix/src/master/master_wakeup.c @@ -58,10 +58,13 @@ #include <msg.h> #include <trigger.h> #include <events.h> +#include <set_eugid.h> +#include <set_ugid.h> /* Global library. */ #include <mail_proto.h> /* triggers */ +#include <mail_params.h> /* Application-specific. */ @@ -101,8 +104,30 @@ static void master_wakeup_timer_event(int unused_event, char *context) case MASTER_SERV_TYPE_UNIX: status = LOCAL_TRIGGER(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); break; + + /* + * If someone compromises the postfix account then this must not + * overwrite files outside the chroot jail. Countermeasures: + * + * - Limit the damage by accessing the FIFO as postfix not root. + * + * - Have fifo_trigger() call safe_open() so we won't follow + * arbitrary hard/symlinks to files in/outside the chroot jail. + * + * - All non-chroot postfix-related files must be root owned (or + * postfix check complains). + * + * - The postfix user and group ID must not be shared with other + * applications (says the INSTALL documentation). + * + * Result of a discussion with Michael Tokarev, who received his + * insights from Solar Designer, who tested Postfix with a kernel + * module that is paranoid about open() calls. + */ case MASTER_SERV_TYPE_FIFO: + set_eugid(var_owner_uid, var_owner_gid); status = fifo_trigger(serv->name, &wakeup, sizeof(wakeup), BRIEFLY); + set_ugid(getuid(), getgid()); break; default: msg_panic("%s: unknown service type: %d", myname, serv->type); diff --git a/gnu/dist/postfix/src/master/multi_server.c b/gnu/dist/postfix/src/master/multi_server.c index c9df296f4f9..602db54c71a 100644 --- a/gnu/dist/postfix/src/master/multi_server.c +++ b/gnu/dist/postfix/src/master/multi_server.c @@ -95,6 +95,9 @@ /* Function to be executed prior to accepting a new connection. /* .sp /* Only the last instance of this parameter type is remembered. +/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. /* .PP /* multi_server_disconnect() should be called by the application /* when a client disconnects. @@ -168,6 +171,7 @@ #include <mail_conf.h> #include <timed_ipc.h> #include <resolve_local.h> +#include <mail_flow.h> /* Process manager. */ @@ -190,6 +194,7 @@ static void (*multi_server_accept) (int, char *); static void (*multi_server_onexit) (char *, char **); static void (*multi_server_pre_accept) (char *, char **); static VSTREAM *multi_server_lock; +static int multi_server_in_flow_delay; /* multi_server_exit - normal termination */ @@ -253,11 +258,21 @@ static void multi_server_execute(int unused_event, char *context) event_request_timer(multi_server_timeout, (char *) 0, var_idle_limit); } +/* multi_server_enable_read - enable read events */ + +static void multi_server_enable_read(int unused_event, char *context) +{ + VSTREAM *stream = (VSTREAM *) context; + + event_enable_read(vstream_fileno(stream), multi_server_execute, (char *) stream); +} + /* multi_server_wakeup - wake up application */ static void multi_server_wakeup(int fd) { VSTREAM *stream; + char *tmp; if (msg_verbose) msg_info("connection established fd %d", fd); @@ -265,8 +280,15 @@ static void multi_server_wakeup(int fd) close_on_exec(fd, CLOSE_ON_EXEC); client_count++; stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(multi_server_name, " socket", (char *) 0); + vstream_control(stream, VSTREAM_CTL_PATH, tmp, VSTREAM_CTL_END); + myfree(tmp); timed_ipc_setup(stream); - event_enable_read(fd, multi_server_execute, (char *) stream); + if (multi_server_in_flow_delay && mail_flow_get(1) < 0) + event_request_timer(multi_server_enable_read, (char *) stream, + var_in_flow_delay); + else + multi_server_enable_read(0, (char *) stream); } /* multi_server_accept_local - accept client connection request */ @@ -498,6 +520,9 @@ NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) case MAIL_SERVER_PRE_ACCEPT: multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; + case MAIL_SERVER_IN_FLOW_DELAY: + multi_server_in_flow_delay = 1; + break; default: msg_panic("%s: unknown argument type: %d", myname, key); } @@ -611,6 +636,8 @@ NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...) } event_enable_read(MASTER_STATUS_FD, multi_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (char *) 0); /* diff --git a/gnu/dist/postfix/src/master/single_server.c b/gnu/dist/postfix/src/master/single_server.c index 9c6920a7d73..d110e40102c 100644 --- a/gnu/dist/postfix/src/master/single_server.c +++ b/gnu/dist/postfix/src/master/single_server.c @@ -92,6 +92,9 @@ /* Function to be executed prior to accepting a new connection. /* .sp /* Only the last instance of this parameter type is remembered. +/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. /* .PP /* The var_use_limit variable limits the number of clients that /* a server can service before it commits suicide. @@ -162,6 +165,7 @@ #include <mail_conf.h> #include <timed_ipc.h> #include <resolve_local.h> +#include <mail_flow.h> /* Process manager. */ @@ -183,6 +187,7 @@ static void (*single_server_accept) (int, char *); static void (*single_server_onexit) (char *, char **); static void (*single_server_pre_accept) (char *, char **); static VSTREAM *single_server_lock; +static int single_server_in_flow_delay; /* single_server_exit - normal termination */ @@ -216,6 +221,7 @@ static void single_server_timeout(int unused_event, char *unused_context) static void single_server_wakeup(int fd) { VSTREAM *stream; + char *tmp; /* * If the accept() succeeds, be sure to disable non-blocking I/O, because @@ -228,9 +234,14 @@ static void single_server_wakeup(int fd) non_blocking(fd, BLOCKING); close_on_exec(fd, CLOSE_ON_EXEC); stream = vstream_fdopen(fd, O_RDWR); + tmp = concatenate(single_server_name, " socket", (char *) 0); + vstream_control(stream, VSTREAM_CTL_PATH, tmp, VSTREAM_CTL_END); + myfree(tmp); timed_ipc_setup(stream); if (master_notify(var_pid, MASTER_STAT_TAKEN) < 0) single_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (single_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); single_server_service(stream, single_server_name, single_server_argv); (void) vstream_fclose(stream); if (master_notify(var_pid, MASTER_STAT_AVAIL) < 0) @@ -469,6 +480,9 @@ NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) case MAIL_SERVER_PRE_ACCEPT: single_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; + case MAIL_SERVER_IN_FLOW_DELAY: + single_server_in_flow_delay = 1; + break; default: msg_panic("%s: unknown argument type: %d", myname, key); } @@ -582,6 +596,8 @@ NORETURN single_server_main(int argc, char **argv, SINGLE_SERVER_FN service,...) } event_enable_read(MASTER_STATUS_FD, single_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (char *) 0); /* diff --git a/gnu/dist/postfix/src/master/trigger_server.c b/gnu/dist/postfix/src/master/trigger_server.c index 7edd05ff6d6..c84b8a8b519 100644 --- a/gnu/dist/postfix/src/master/trigger_server.c +++ b/gnu/dist/postfix/src/master/trigger_server.c @@ -99,6 +99,9 @@ /* Function to be executed prior to accepting a new request. /* .sp /* Only the last instance of this parameter type is remembered. +/* .IP "MAIL_SERVER_IN_FLOW_DELAY (none)" +/* Pause $in_flow_delay seconds when no "mail flow control token" +/* is available. A token is consumed for each connection request. /* .PP /* The var_use_limit variable limits the number of clients that /* a server can service before it commits suicide. @@ -169,6 +172,7 @@ #include <debug_process.h> #include <mail_conf.h> #include <resolve_local.h> +#include <mail_flow.h> /* Process manager. */ @@ -190,6 +194,7 @@ static void (*trigger_server_accept) (int, char *); static void (*trigger_server_onexit) (char *, char **); static void (*trigger_server_pre_accept) (char *, char **); static VSTREAM *trigger_server_lock; +static int trigger_server_in_flow_delay; /* trigger_server_exit - normal termination */ @@ -230,6 +235,8 @@ static void trigger_server_wakeup(int fd) */ if (master_notify(var_pid, MASTER_STAT_TAKEN) < 0) trigger_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); + if (trigger_server_in_flow_delay && mail_flow_get(1) < 0) + doze(var_in_flow_delay * 1000000); if ((len = read(fd, buf, sizeof(buf))) >= 0) trigger_server_service(buf, len, trigger_server_name, trigger_server_argv); @@ -469,6 +476,9 @@ NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,.. case MAIL_SERVER_PRE_ACCEPT: trigger_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN); break; + case MAIL_SERVER_IN_FLOW_DELAY: + trigger_server_in_flow_delay = 1; + break; default: msg_panic("%s: unknown argument type: %d", myname, key); } @@ -593,6 +603,8 @@ NORETURN trigger_server_main(int argc, char **argv, TRIGGER_SERVER_FN service,.. } event_enable_read(MASTER_STATUS_FD, trigger_server_abort, (char *) 0); close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC); + close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC); watchdog = watchdog_create(1000, (WATCHDOG_FN) 0, (char *) 0); /* diff --git a/gnu/dist/postfix/src/nqmgr/.printfck b/gnu/dist/postfix/src/nqmgr/.printfck new file mode 100644 index 00000000000..66016ed453c --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/gnu/dist/postfix/src/nqmgr/Makefile.in b/gnu/dist/postfix/src/nqmgr/Makefile.in new file mode 100644 index 00000000000..7396348c878 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/Makefile.in @@ -0,0 +1,296 @@ +SHELL = /bin/sh +SRCS = qmgr.c qmgr_active.c qmgr_transport.c qmgr_queue.c qmgr_entry.c \ + qmgr_message.c qmgr_deliver.c qmgr_move.c qmgr_rcpt_list.c \ + qmgr_job.c qmgr_peer.c \ + qmgr_defer.c qmgr_enable.c qmgr_scan.c qmgr_bounce.c +OBJS = qmgr.o qmgr_active.o qmgr_transport.o qmgr_queue.o qmgr_entry.o \ + qmgr_message.o qmgr_deliver.o qmgr_move.o qmgr_rcpt_list.o \ + qmgr_job.o qmgr_peer.o \ + qmgr_defer.o qmgr_enable.o qmgr_scan.o qmgr_bounce.o +HDRS = qmgr.h +TESTSRC = +WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ + -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ + -Wunused +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = qmgr +INC_DIR = ../../include +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libutil.a + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +Makefile: Makefile.in + (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ + +test: $(TESTPROG) + +update: ../../libexec/n$(PROG) + +../../libexec/n$(PROG): $(PROG) + cp $(PROG) ../../libexec/n$(PROG) + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \ + done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +qmgr.o: qmgr.c +qmgr.o: ../../include/sys_defs.h +qmgr.o: ../../include/msg.h +qmgr.o: ../../include/events.h +qmgr.o: ../../include/vstream.h +qmgr.o: ../../include/vbuf.h +qmgr.o: ../../include/dict.h +qmgr.o: ../../include/argv.h +qmgr.o: ../../include/mail_queue.h +qmgr.o: ../../include/vstring.h +qmgr.o: ../../include/recipient_list.h +qmgr.o: ../../include/mail_conf.h +qmgr.o: ../../include/mail_params.h +qmgr.o: ../../include/mail_proto.h +qmgr.o: ../../include/iostuff.h +qmgr.o: ../../include/attr.h +qmgr.o: ../../include/mail_flow.h +qmgr.o: ../../include/master_proto.h +qmgr.o: ../../include/mail_server.h +qmgr.o: qmgr.h +qmgr.o: ../../include/scan_dir.h +qmgr.o: ../../include/maps.h +qmgr_active.o: qmgr_active.c +qmgr_active.o: ../../include/sys_defs.h +qmgr_active.o: ../../include/msg.h +qmgr_active.o: ../../include/events.h +qmgr_active.o: ../../include/mymalloc.h +qmgr_active.o: ../../include/vstream.h +qmgr_active.o: ../../include/vbuf.h +qmgr_active.o: ../../include/mail_params.h +qmgr_active.o: ../../include/mail_open_ok.h +qmgr_active.o: ../../include/mail_queue.h +qmgr_active.o: ../../include/vstring.h +qmgr_active.o: ../../include/recipient_list.h +qmgr_active.o: ../../include/bounce.h +qmgr_active.o: ../../include/defer.h +qmgr_active.o: ../../include/abounce.h +qmgr_active.o: ../../include/rec_type.h +qmgr_active.o: qmgr.h +qmgr_active.o: ../../include/scan_dir.h +qmgr_active.o: ../../include/maps.h +qmgr_active.o: ../../include/dict.h +qmgr_active.o: ../../include/argv.h +qmgr_bounce.o: qmgr_bounce.c +qmgr_bounce.o: ../../include/sys_defs.h +qmgr_bounce.o: ../../include/bounce.h +qmgr_bounce.o: ../../include/deliver_completed.h +qmgr_bounce.o: ../../include/vstream.h +qmgr_bounce.o: ../../include/vbuf.h +qmgr_bounce.o: qmgr.h +qmgr_bounce.o: ../../include/scan_dir.h +qmgr_bounce.o: ../../include/maps.h +qmgr_bounce.o: ../../include/dict.h +qmgr_bounce.o: ../../include/argv.h +qmgr_defer.o: qmgr_defer.c +qmgr_defer.o: ../../include/sys_defs.h +qmgr_defer.o: ../../include/msg.h +qmgr_defer.o: ../../include/vstream.h +qmgr_defer.o: ../../include/vbuf.h +qmgr_defer.o: ../../include/defer.h +qmgr_defer.o: ../../include/bounce.h +qmgr_defer.o: qmgr.h +qmgr_defer.o: ../../include/scan_dir.h +qmgr_defer.o: ../../include/maps.h +qmgr_defer.o: ../../include/dict.h +qmgr_defer.o: ../../include/argv.h +qmgr_deliver.o: qmgr_deliver.c +qmgr_deliver.o: ../../include/sys_defs.h +qmgr_deliver.o: ../../include/msg.h +qmgr_deliver.o: ../../include/vstring.h +qmgr_deliver.o: ../../include/vbuf.h +qmgr_deliver.o: ../../include/vstream.h +qmgr_deliver.o: ../../include/vstring_vstream.h +qmgr_deliver.o: ../../include/events.h +qmgr_deliver.o: ../../include/iostuff.h +qmgr_deliver.o: ../../include/mail_queue.h +qmgr_deliver.o: ../../include/mail_proto.h +qmgr_deliver.o: ../../include/attr.h +qmgr_deliver.o: ../../include/recipient_list.h +qmgr_deliver.o: ../../include/mail_params.h +qmgr_deliver.o: ../../include/deliver_request.h +qmgr_deliver.o: ../../include/verp_sender.h +qmgr_deliver.o: qmgr.h +qmgr_deliver.o: ../../include/scan_dir.h +qmgr_deliver.o: ../../include/maps.h +qmgr_deliver.o: ../../include/dict.h +qmgr_deliver.o: ../../include/argv.h +qmgr_enable.o: qmgr_enable.c +qmgr_enable.o: ../../include/sys_defs.h +qmgr_enable.o: ../../include/msg.h +qmgr_enable.o: ../../include/vstream.h +qmgr_enable.o: ../../include/vbuf.h +qmgr_enable.o: qmgr.h +qmgr_enable.o: ../../include/scan_dir.h +qmgr_enable.o: ../../include/maps.h +qmgr_enable.o: ../../include/dict.h +qmgr_enable.o: ../../include/argv.h +qmgr_entry.o: qmgr_entry.c +qmgr_entry.o: ../../include/sys_defs.h +qmgr_entry.o: ../../include/msg.h +qmgr_entry.o: ../../include/mymalloc.h +qmgr_entry.o: ../../include/events.h +qmgr_entry.o: ../../include/vstream.h +qmgr_entry.o: ../../include/vbuf.h +qmgr_entry.o: ../../include/mail_params.h +qmgr_entry.o: qmgr.h +qmgr_entry.o: ../../include/scan_dir.h +qmgr_entry.o: ../../include/maps.h +qmgr_entry.o: ../../include/dict.h +qmgr_entry.o: ../../include/argv.h +qmgr_job.o: qmgr_job.c +qmgr_job.o: ../../include/sys_defs.h +qmgr_job.o: ../../include/msg.h +qmgr_job.o: ../../include/htable.h +qmgr_job.o: ../../include/mymalloc.h +qmgr_job.o: ../../include/sane_time.h +qmgr_job.o: qmgr.h +qmgr_job.o: ../../include/vstream.h +qmgr_job.o: ../../include/vbuf.h +qmgr_job.o: ../../include/scan_dir.h +qmgr_job.o: ../../include/maps.h +qmgr_job.o: ../../include/dict.h +qmgr_job.o: ../../include/argv.h +qmgr_message.o: qmgr_message.c +qmgr_message.o: ../../include/sys_defs.h +qmgr_message.o: ../../include/msg.h +qmgr_message.o: ../../include/mymalloc.h +qmgr_message.o: ../../include/vstring.h +qmgr_message.o: ../../include/vbuf.h +qmgr_message.o: ../../include/vstream.h +qmgr_message.o: ../../include/split_at.h +qmgr_message.o: ../../include/valid_hostname.h +qmgr_message.o: ../../include/argv.h +qmgr_message.o: ../../include/stringops.h +qmgr_message.o: ../../include/myflock.h +qmgr_message.o: ../../include/sane_time.h +qmgr_message.o: ../../include/dict.h +qmgr_message.o: ../../include/mail_queue.h +qmgr_message.o: ../../include/mail_params.h +qmgr_message.o: ../../include/canon_addr.h +qmgr_message.o: ../../include/record.h +qmgr_message.o: ../../include/rec_type.h +qmgr_message.o: ../../include/sent.h +qmgr_message.o: ../../include/deliver_completed.h +qmgr_message.o: ../../include/mail_addr_find.h +qmgr_message.o: ../../include/maps.h +qmgr_message.o: ../../include/opened.h +qmgr_message.o: ../../include/resolve_local.h +qmgr_message.o: ../../include/verp_sender.h +qmgr_message.o: ../../include/resolve_clnt.h +qmgr_message.o: qmgr.h +qmgr_message.o: ../../include/scan_dir.h +qmgr_move.o: qmgr_move.c +qmgr_move.o: ../../include/sys_defs.h +qmgr_move.o: ../../include/msg.h +qmgr_move.o: ../../include/scan_dir.h +qmgr_move.o: ../../include/recipient_list.h +qmgr_move.o: ../../include/mail_queue.h +qmgr_move.o: ../../include/vstring.h +qmgr_move.o: ../../include/vbuf.h +qmgr_move.o: ../../include/vstream.h +qmgr_move.o: ../../include/mail_scan_dir.h +qmgr_move.o: qmgr.h +qmgr_move.o: ../../include/maps.h +qmgr_move.o: ../../include/dict.h +qmgr_move.o: ../../include/argv.h +qmgr_peer.o: qmgr_peer.c +qmgr_peer.o: ../../include/sys_defs.h +qmgr_peer.o: ../../include/msg.h +qmgr_peer.o: ../../include/htable.h +qmgr_peer.o: ../../include/mymalloc.h +qmgr_peer.o: qmgr.h +qmgr_peer.o: ../../include/vstream.h +qmgr_peer.o: ../../include/vbuf.h +qmgr_peer.o: ../../include/scan_dir.h +qmgr_peer.o: ../../include/maps.h +qmgr_peer.o: ../../include/dict.h +qmgr_peer.o: ../../include/argv.h +qmgr_queue.o: qmgr_queue.c +qmgr_queue.o: ../../include/sys_defs.h +qmgr_queue.o: ../../include/msg.h +qmgr_queue.o: ../../include/mymalloc.h +qmgr_queue.o: ../../include/events.h +qmgr_queue.o: ../../include/htable.h +qmgr_queue.o: ../../include/mail_params.h +qmgr_queue.o: ../../include/recipient_list.h +qmgr_queue.o: qmgr.h +qmgr_queue.o: ../../include/vstream.h +qmgr_queue.o: ../../include/vbuf.h +qmgr_queue.o: ../../include/scan_dir.h +qmgr_queue.o: ../../include/maps.h +qmgr_queue.o: ../../include/dict.h +qmgr_queue.o: ../../include/argv.h +qmgr_rcpt_list.o: qmgr_rcpt_list.c +qmgr_rcpt_list.o: ../../include/sys_defs.h +qmgr_rcpt_list.o: ../../include/mymalloc.h +qmgr_rcpt_list.o: qmgr.h +qmgr_rcpt_list.o: ../../include/vstream.h +qmgr_rcpt_list.o: ../../include/vbuf.h +qmgr_rcpt_list.o: ../../include/scan_dir.h +qmgr_rcpt_list.o: ../../include/maps.h +qmgr_rcpt_list.o: ../../include/dict.h +qmgr_rcpt_list.o: ../../include/argv.h +qmgr_scan.o: qmgr_scan.c +qmgr_scan.o: ../../include/sys_defs.h +qmgr_scan.o: ../../include/msg.h +qmgr_scan.o: ../../include/mymalloc.h +qmgr_scan.o: ../../include/scan_dir.h +qmgr_scan.o: ../../include/mail_scan_dir.h +qmgr_scan.o: qmgr.h +qmgr_scan.o: ../../include/vstream.h +qmgr_scan.o: ../../include/vbuf.h +qmgr_scan.o: ../../include/maps.h +qmgr_scan.o: ../../include/dict.h +qmgr_scan.o: ../../include/argv.h +qmgr_transport.o: qmgr_transport.c +qmgr_transport.o: ../../include/sys_defs.h +qmgr_transport.o: ../../include/msg.h +qmgr_transport.o: ../../include/htable.h +qmgr_transport.o: ../../include/events.h +qmgr_transport.o: ../../include/mymalloc.h +qmgr_transport.o: ../../include/vstream.h +qmgr_transport.o: ../../include/vbuf.h +qmgr_transport.o: ../../include/iostuff.h +qmgr_transport.o: ../../include/mail_proto.h +qmgr_transport.o: ../../include/attr.h +qmgr_transport.o: ../../include/recipient_list.h +qmgr_transport.o: ../../include/mail_conf.h +qmgr_transport.o: ../../include/mail_params.h +qmgr_transport.o: qmgr.h +qmgr_transport.o: ../../include/scan_dir.h +qmgr_transport.o: ../../include/maps.h +qmgr_transport.o: ../../include/dict.h +qmgr_transport.o: ../../include/argv.h diff --git a/gnu/dist/postfix/src/nqmgr/qmgr.c b/gnu/dist/postfix/src/nqmgr/qmgr.c new file mode 100644 index 00000000000..e259e47aa06 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr.c @@ -0,0 +1,582 @@ +/*++ +/* NAME +/* qmgr 8 +/* SUMMARY +/* Postfix queue manager +/* SYNOPSIS +/* \fBqmgr\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBqmgr\fR daemon awaits the arrival of incoming mail +/* and arranges for its delivery via Postfix delivery processes. +/* The actual mail routing strategy is delegated to the +/* \fBtrivial-rewrite\fR(8) daemon. +/* This program expects to be run from the \fBmaster\fR(8) process +/* manager. +/* +/* Mail addressed to the local \fBdouble-bounce\fR address is silently +/* discarded. This stops potential loops caused by undeliverable +/* bounce notifications. +/* +/* Mail addressed to a user listed in the optional \fBrelocated\fR +/* database is bounced with a "user has moved to \fInew_location\fR" +/* message. See \fBrelocated\fR(5) for a precise description. +/* MAIL QUEUES +/* .ad +/* .fi +/* The \fBqmgr\fR daemon maintains the following queues: +/* .IP \fBincoming\fR +/* Inbound mail from the network, or mail picked up by the +/* local \fBpickup\fR agent from the \fBmaildrop\fR directory. +/* .IP \fBactive\fR +/* Messages that the queue manager has opened for delivery. Only +/* a limited number of messages is allowed to enter the \fBactive\fR +/* queue (leaky bucket strategy, for a fixed delivery rate). +/* .IP \fBdeferred\fR +/* Mail that could not be delivered upon the first attempt. The queue +/* manager implements exponential backoff by doubling the time between +/* delivery attempts. +/* .IP \fBcorrupt\fR +/* Unreadable or damaged queue files are moved here for inspection. +/* DELIVERY STATUS REPORTS +/* .ad +/* .fi +/* The \fBqmgr\fR daemon keeps an eye on per-message delivery status +/* reports in the following directories. Each status report file has +/* the same name as the corresponding message file: +/* .IP \fBbounce\fR +/* Per-recipient status information about why mail is bounced. +/* These files are maintained by the \fBbounce\fR(8) daemon. +/* .IP \fBdefer\fR +/* Per-recipient status information about why mail is delayed. +/* These files are maintained by the \fBdefer\fR(8) daemon. +/* .PP +/* The \fBqmgr\fR daemon is responsible for asking the +/* \fBbounce\fR(8) or \fBdefer\fR(8) daemons to send non-delivery +/* reports. +/* STRATEGIES +/* .ad +/* .fi +/* The queue manager implements a variety of strategies for +/* either opening queue files (input) or for message delivery (output). +/* .IP "\fBleaky bucket\fR" +/* This strategy limits the number of messages in the \fBactive\fR queue +/* and prevents the queue manager from running out of memory under +/* heavy load. +/* .IP \fBfairness\fR +/* When the \fBactive\fR queue has room, the queue manager takes one +/* message from the \fBincoming\fR queue and one from the \fBdeferred\fR +/* queue. This prevents a large mail backlog from blocking the delivery +/* of new mail. +/* .IP "\fBslow start\fR" +/* This strategy eliminates "thundering herd" problems by slowly +/* adjusting the number of parallel deliveries to the same destination. +/* .IP "\fBround robin\fR +/* The queue manager sorts delivery requests by destination. +/* Round-robin selection prevents one destination from dominating +/* deliveries to other destinations. +/* .IP "\fBexponential backoff\fR" +/* Mail that cannot be delivered upon the first attempt is deferred. +/* The time interval between delivery attempts is doubled after each +/* attempt. +/* .IP "\fBdestination status cache\fR" +/* The queue manager avoids unnecessary delivery attempts by +/* maintaining a short-term, in-memory list of unreachable destinations. +/* .IP "\fBpreemptive message scheduling\fR" +/* The queue manager attempts to minimize the average per-recipient delay +/* while still preserving the correct per-message delays, using +/* a sophisticated preemptive message scheduling. +/* TRIGGERS +/* .ad +/* .fi +/* On an idle system, the queue manager waits for the arrival of +/* trigger events, or it waits for a timer to go off. A trigger +/* is a one-byte message. +/* Depending on the message received, the queue manager performs +/* one of the following actions (the message is followed by the +/* symbolic constant used internally by the software): +/* .IP "\fBD (QMGR_REQ_SCAN_DEFERRED)\fR" +/* Start a deferred queue scan. If a deferred queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBI (QMGR_REQ_SCAN_INCOMING)\fR" +/* Start an incoming queue scan. If an incoming queue scan is already +/* in progress, that scan will be restarted as soon as it finishes. +/* .IP "\fBA (QMGR_REQ_SCAN_ALL)\fR" +/* Ignore deferred queue file time stamps. The request affects +/* the next deferred queue scan. +/* .IP "\fBF (QMGR_REQ_FLUSH_DEAD)\fR" +/* Purge all information about dead transports and destinations. +/* .IP "\fBW (TRIGGER_REQ_WAKEUP)\fR" +/* Wakeup call, This is used by the master server to instantiate +/* servers that should not go away forever. The action is to start +/* an incoming queue scan. +/* .PP +/* The \fBqmgr\fR daemon reads an entire buffer worth of triggers. +/* Multiple identical trigger requests are collapsed into one, and +/* trigger requests are sorted so that \fBA\fR and \fBF\fR precede +/* \fBD\fR and \fBI\fR. Thus, in order to force a deferred queue run, +/* one would request \fBA F D\fR; in order to notify the queue manager +/* of the arrival of new mail one would request \fBI\fR. +/* STANDARDS +/* .ad +/* .fi +/* None. The \fBqmgr\fR daemon does not interact with the outside world. +/* SECURITY +/* .ad +/* .fi +/* The \fBqmgr\fR daemon is not security sensitive. It reads +/* single-character messages from untrusted local users, and thus may +/* be susceptible to denial of service attacks. The \fBqmgr\fR daemon +/* does not talk to the outside world, and it can be run at fixed low +/* privilege in a chrooted environment. +/* DIAGNOSTICS +/* Problems and transactions are logged to the syslog daemon. +/* Corrupted message files are saved to the \fBcorrupt\fR queue +/* for further inspection. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* BUGS +/* A single queue manager process has to compete for disk access with +/* multiple front-end processes such as \fBsmtpd\fR. A sudden burst of +/* inbound mail can negatively impact outbound delivery rates. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. See the Postfix \fBmain.cf\fR file for syntax details +/* and for default values. Use the \fBpostfix reload\fR command after +/* a configuration change. +/* .SH Miscellaneous +/* .ad +/* .fi +/* .IP \fBallow_min_user\fR +/* Do not bounce recipient addresses that begin with '-'. +/* .IP \fBrelocated_maps\fR +/* Tables with contact information for users, hosts or domains +/* that no longer exist. See \fBrelocated\fR(5). +/* .IP \fBqueue_directory\fR +/* Top-level directory of the Postfix queue. +/* .SH "Active queue controls" +/* .ad +/* .fi +/* In the text below, \fItransport\fR is the first field in a +/* \fBmaster.cf\fR entry. +/* .IP \fBqmgr_message_active_limit\fR +/* Limit the number of messages in the active queue. +/* .IP \fBqmgr_message_recipient_limit\fR +/* Limit the number of in-memory recipients. +/* .sp +/* This parameter also limits the size of the short-term, in-memory +/* destination cache. +/* .IP \fBqmgr_message_recipient_minimum\fR +/* Per message minimum of in-memory recipients. +/* .IP \fBdefault_recipient_limit\fR +/* Default limit on the number of in-memory recipients per transport. +/* .IP \fItransport\fB_recipient_limit\fR +/* Limit on the number of in-memory recipients, for the named +/* message \fItransport\fR. +/* .IP \fBdefault_extra_recipient_limit\fR +/* Default limit on the total number of per transport in-memory +/* recipients that the preempting messages can have. +/* .IP \fItransport\fB_extra_recipient_limit\fR +/* Limit on the number of in-memory recipients which all preempting +/* messages delivered by the transport \fItransport\fR can have. +/* .SH "Timing controls" +/* .ad +/* .fi +/* .IP \fBmin_backoff\fR +/* Minimal time in seconds between delivery attempts +/* of a deferred message. +/* .sp +/* This parameter also limits the time an unreachable destination +/* is kept in the short-term, in-memory destination status cache. +/* .IP \fBmax_backoff\fR +/* Maximal time in seconds between delivery attempts +/* of a deferred message. +/* .IP \fBmaximal_queue_lifetime\fR +/* Maximal time in days a message is queued +/* before it is sent back as undeliverable. +/* .IP \fBqueue_run_delay\fR +/* Time in seconds between deferred queue scans. Queue scans do +/* not overlap. +/* .IP \fBtransport_retry_time\fR +/* Time in seconds between attempts to contact a broken +/* delivery transport. +/* .SH "Concurrency controls" +/* .ad +/* .fi +/* .IP \fBinitial_destination_concurrency\fR +/* Initial per-destination concurrency level for parallel delivery +/* to the same destination. +/* .IP \fBdefault_destination_concurrency_limit\fR +/* Default limit on the number of parallel deliveries to the same +/* destination. +/* .IP \fItransport\fB_destination_concurrency_limit\fR +/* Limit on the number of parallel deliveries to the same destination, +/* for delivery via the named message \fItransport\fR. +/* .SH "Recipient controls" +/* .ad +/* .fi +/* .IP \fBdefault_destination_recipient_limit\fR +/* Default limit on the number of recipients per message transfer. +/* .IP \fItransport\fB_destination_recipient_limit\fR +/* Limit on the number of recipients per message transfer, for the +/* named message \fItransport\fR. +/* .SH "Message scheduling" +/* .ad +/* .fi +/* .IP "\fItransport\fB_delivery_slot_cost\fR (valid range: 0,2,3...) +/* This parameter basically controls how often a message +/* delivered by \fItransport\fR can be preempted by another +/* message. +/* An internal per-message/transport counter is incremented by one +/* for each \fItransport\fB_delivery_slot_cost\fR +/* deliveries handled by \fItransport\fR. This counter represents +/* the number of "available delivery slots" for use by other messages. +/* Current message can be preempted by another message when that +/* other message can be delivered using less \fItransport\fR agents +/* than the value of the "available delivery slots" counter. +/* .sp +/* Value equal to 0 disables the message preemption for \fItransport\fR. +/* .IP \fItransport\fB_minimum_delivery_slots\fR +/* Message preemption is not attempted at all whenever a message +/* that can't ever accumulate at least \fItransport\fB_minimum_delivery_slots\fR +/* available delivery slots is being delivered by \fItransport\fR. +/* .IP "\fItransport\fB_delivery_slot_discount\fR (valid range: 0..100)" +/* .IP \fItransport\fB_delivery_slot_loan\fR +/* These parameters speed up the moment when a message preemption can happen. +/* Instead of waiting until the full amount of delivery slots +/* required is available, the preemption can happen when +/* \fItransport\fB_delivery_slot_discount\fR percent of the required +/* amount plus \fItransport\fB_delivery_slot_loan\fR still remains to +/* be accumulated. Note that the full amount will still have to be +/* accumulated before another preemption can take place later. +/* .IP \fBdefault_delivery_slot_cost\fR +/* .IP \fBdefault_minimum_delivery_slots\fR +/* .IP \fBdefault_delivery_slot_discount\fR +/* .IP \fBdefault_delivery_slot_loan\fR +/* Default values for the transport specific parameters described above. +/* SEE ALSO +/* master(8), process manager +/* relocated(5), format of the "user has moved" table +/* syslogd(8) system logging +/* trivial-rewrite(8), address routing +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <events.h> +#include <vstream.h> +#include <dict.h> + +/* Global library. */ + +#include <mail_queue.h> +#include <recipient_list.h> +#include <mail_conf.h> +#include <mail_params.h> +#include <mail_proto.h> /* QMGR_SCAN constants */ +#include <mail_flow.h> + +/* Master process interface */ + +#include <master_proto.h> +#include <mail_server.h> + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * Tunables. + */ +int var_queue_run_delay; +int var_min_backoff_time; +int var_max_backoff_time; +int var_max_queue_time; +int var_qmgr_active_limit; +int var_qmgr_rcpt_limit; +int var_qmgr_msg_rcpt_limit; +int var_xport_rcpt_limit; +int var_stack_rcpt_limit; +int var_delivery_slot_cost; +int var_delivery_slot_loan; +int var_delivery_slot_discount; +int var_min_delivery_slots; +int var_init_dest_concurrency; +int var_transport_retry_time; +int var_dest_con_limit; +int var_dest_rcpt_limit; +char *var_relocated_maps; +char *var_virtual_maps; +char *var_defer_xports; +bool var_allow_min_user; +int var_local_con_lim; +int var_local_rcpt_lim; +int var_proc_limit; +bool var_verp_bounce_off; + +static QMGR_SCAN *qmgr_incoming; +static QMGR_SCAN *qmgr_deferred; + +MAPS *qmgr_relocated; +MAPS *qmgr_virtual; + +/* qmgr_deferred_run_event - queue manager heartbeat */ + +static void qmgr_deferred_run_event(int unused_event, char *dummy) +{ + + /* + * This routine runs when it is time for another deferred queue scan. + * Make sure this routine gets called again in the future. + */ + qmgr_scan_request(qmgr_deferred, QMGR_SCAN_START); + event_request_timer(qmgr_deferred_run_event, dummy, var_queue_run_delay); +} + +/* qmgr_trigger_event - respond to external trigger(s) */ + +static void qmgr_trigger_event(char *buf, int len, + char *unused_service, char **argv) +{ + int incoming_flag = 0; + int deferred_flag = 0; + int i; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Collapse identical requests that have arrived since we looked last + * time. There is no client feedback so there is no need to process each + * request in order. And as long as we don't have conflicting requests we + * are free to sort them into the most suitable order. + */ + for (i = 0; i < len; i++) { + if (msg_verbose) + msg_info("request: %d (%c)", + buf[i], ISALNUM(buf[i]) ? buf[i] : '?'); + switch (buf[i]) { + case TRIGGER_REQ_WAKEUP: + case QMGR_REQ_SCAN_INCOMING: + incoming_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_SCAN_DEFERRED: + deferred_flag |= QMGR_SCAN_START; + break; + case QMGR_REQ_FLUSH_DEAD: + deferred_flag |= QMGR_FLUSH_DEAD; + incoming_flag |= QMGR_FLUSH_DEAD; + break; + case QMGR_REQ_SCAN_ALL: + deferred_flag |= QMGR_SCAN_ALL; + incoming_flag |= QMGR_SCAN_ALL; + break; + default: + if (msg_verbose) + msg_info("request ignored"); + break; + } + } + + /* + * Process each request type at most once. Modifiers take effect upon the + * next queue run. If no queue run is in progress, and a queue scan is + * requested, the request takes effect immediately. + */ + if (incoming_flag != 0) + qmgr_scan_request(qmgr_incoming, incoming_flag); + if (deferred_flag != 0) + qmgr_scan_request(qmgr_deferred, deferred_flag); +} + +/* qmgr_loop - queue manager main loop */ + +static int qmgr_loop(char *unused_name, char **unused_argv) +{ + char *in_path = 0; + char *df_path = 0; + int token_count; + int in_feed = 0; + + /* + * This routine runs as part of the event handling loop, after the event + * manager has delivered a timer or I/O event (including the completion + * of a connection to a delivery process), or after it has waited for a + * specified amount of time. The result value of qmgr_loop() specifies + * how long the event manager should wait for the next event. + */ +#define DONT_WAIT 0 +#define WAIT_FOR_EVENT (-1) + + /* + * Attempt to drain the active queue by allocating a suitable delivery + * process and by delivering mail via it. Delivery process allocation and + * mail delivery are asynchronous. + */ + qmgr_active_drain(); + + /* + * Let some new blood into the active queue when the queue size is + * smaller than some configurable limit. When the system is under heavy + * load, favor new mail over old mail. + */ + if (qmgr_message_count < var_qmgr_active_limit) + if ((in_path = qmgr_scan_next(qmgr_incoming)) != 0) + in_feed = qmgr_active_feed(qmgr_incoming, in_path); + if (qmgr_message_count < var_qmgr_active_limit) + if ((df_path = qmgr_scan_next(qmgr_deferred)) != 0) + qmgr_active_feed(qmgr_deferred, df_path); + + /* + * Global flow control. If enabled, slow down receiving processes that + * get ahead of the queue manager, but don't block them completely. + */ + if (var_in_flow_delay > 0) { + token_count = mail_flow_count(); + if (token_count < var_proc_limit) { + if (in_feed != 0) + mail_flow_put(1); + else if (qmgr_incoming->handle == 0) + mail_flow_put(var_proc_limit - token_count); + } else if (token_count > var_proc_limit) { + mail_flow_get(token_count - var_proc_limit); + } + } + if (in_path || df_path) + return (DONT_WAIT); + return (WAIT_FOR_EVENT); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + if (dict_changed()) { + msg_info("table has changed -- exiting"); + exit(0); + } +} + +/* qmgr_pre_init - pre-jail initialization */ + +static void qmgr_pre_init(char *unused_name, char **unused_argv) +{ + if (*var_relocated_maps) + qmgr_relocated = maps_create("relocated", var_relocated_maps, + DICT_FLAG_LOCK); + if (*var_virtual_maps) + qmgr_virtual = maps_create("virtual", var_virtual_maps, + DICT_FLAG_LOCK); +} + +/* qmgr_post_init - post-jail initialization */ + +static void qmgr_post_init(char *unused_name, char **unused_argv) +{ + + /* + * This routine runs after the skeleton code has entered the chroot jail. + * Prevent automatic process suicide after a limited number of client + * requests or after a limited amount of idle time. Move any left-over + * entries from the active queue to the incoming queue, and give them a + * time stamp into the future, in order to allow ongoing deliveries to + * finish first. Start scanning the incoming and deferred queues. + * Left-over active queue entries are moved to the incoming queue because + * the incoming queue has priority; moving left-overs to the deferred + * queue could cause anomalous delays when "postfix reload/start" are + * issued often. + */ + var_use_limit = 0; + var_idle_limit = 0; + qmgr_move(MAIL_QUEUE_ACTIVE, MAIL_QUEUE_INCOMING, event_time()); + qmgr_incoming = qmgr_scan_create(MAIL_QUEUE_INCOMING); + qmgr_deferred = qmgr_scan_create(MAIL_QUEUE_DEFERRED); + qmgr_scan_request(qmgr_incoming, QMGR_SCAN_START); + qmgr_deferred_run_event(0, (char *) 0); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static CONFIG_STR_TABLE str_table[] = { + VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, + VAR_VIRTUAL_MAPS, DEF_VIRTUAL_MAPS, &var_virtual_maps, 0, 0, + VAR_DEFER_XPORTS, DEF_DEFER_XPORTS, &var_defer_xports, 0, 0, + 0, + }; + static CONFIG_TIME_TABLE time_table[] = { + VAR_QUEUE_RUN_DELAY, DEF_QUEUE_RUN_DELAY, &var_queue_run_delay, 1, 0, + VAR_MIN_BACKOFF_TIME, DEF_MIN_BACKOFF_TIME, &var_min_backoff_time, 1, 0, + VAR_MAX_BACKOFF_TIME, DEF_MAX_BACKOFF_TIME, &var_max_backoff_time, 1, 0, + VAR_MAX_QUEUE_TIME, DEF_MAX_QUEUE_TIME, &var_max_queue_time, 1, 8640000, + VAR_XPORT_RETRY_TIME, DEF_XPORT_RETRY_TIME, &var_transport_retry_time, 1, 0, + 0, + }; + static CONFIG_INT_TABLE int_table[] = { + VAR_QMGR_ACT_LIMIT, DEF_QMGR_ACT_LIMIT, &var_qmgr_active_limit, 1, 0, + VAR_QMGR_RCPT_LIMIT, DEF_QMGR_RCPT_LIMIT, &var_qmgr_rcpt_limit, 1, 0, + VAR_QMGR_MSG_RCPT_LIMIT, DEF_QMGR_MSG_RCPT_LIMIT, &var_qmgr_msg_rcpt_limit, 1, 0, + VAR_XPORT_RCPT_LIMIT, DEF_XPORT_RCPT_LIMIT, &var_xport_rcpt_limit, 0, 0, + VAR_STACK_RCPT_LIMIT, DEF_STACK_RCPT_LIMIT, &var_stack_rcpt_limit, 0, 0, + VAR_DELIVERY_SLOT_COST, DEF_DELIVERY_SLOT_COST, &var_delivery_slot_cost, 0, 0, + VAR_DELIVERY_SLOT_LOAN, DEF_DELIVERY_SLOT_LOAN, &var_delivery_slot_loan, 0, 0, + VAR_DELIVERY_SLOT_DISCOUNT, DEF_DELIVERY_SLOT_DISCOUNT, &var_delivery_slot_discount, 0, 100, + VAR_MIN_DELIVERY_SLOTS, DEF_MIN_DELIVERY_SLOTS, &var_min_delivery_slots, 0, 0, + VAR_INIT_DEST_CON, DEF_INIT_DEST_CON, &var_init_dest_concurrency, 1, 0, + VAR_DEST_CON_LIMIT, DEF_DEST_CON_LIMIT, &var_dest_con_limit, 0, 0, + VAR_DEST_RCPT_LIMIT, DEF_DEST_RCPT_LIMIT, &var_dest_rcpt_limit, 0, 0, + VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0, + VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0, + VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0, + 0, + }; + static CONFIG_BOOL_TABLE bool_table[] = { + VAR_ALLOW_MIN_USER, DEF_ALLOW_MIN_USER, &var_allow_min_user, + VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off, + 0, + }; + + /* + * Use the trigger service skeleton, because no-one else should be + * monitoring our service port while this process runs, and because we do + * not talk back to the client. + */ + trigger_server_main(argc, argv, qmgr_trigger_event, + MAIL_SERVER_INT_TABLE, int_table, + MAIL_SERVER_STR_TABLE, str_table, + MAIL_SERVER_BOOL_TABLE, bool_table, + MAIL_SERVER_TIME_TABLE, time_table, + MAIL_SERVER_PRE_INIT, qmgr_pre_init, + MAIL_SERVER_POST_INIT, qmgr_post_init, + MAIL_SERVER_LOOP, qmgr_loop, + MAIL_SERVER_PRE_ACCEPT, pre_accept, + 0); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr.h b/gnu/dist/postfix/src/nqmgr/qmgr.h new file mode 100644 index 00000000000..8749c35605c --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr.h @@ -0,0 +1,431 @@ +/*++ +/* NAME +/* qmgr 3h +/* SUMMARY +/* queue manager data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstream.h> +#include <scan_dir.h> + + /* + * Global library. + */ +#include <maps.h> + + /* + * The queue manager is built around lots of mutually-referring structures. + * These typedefs save some typing. + */ +typedef struct QMGR_TRANSPORT QMGR_TRANSPORT; +typedef struct QMGR_QUEUE QMGR_QUEUE; +typedef struct QMGR_ENTRY QMGR_ENTRY; +typedef struct QMGR_MESSAGE QMGR_MESSAGE; +typedef struct QMGR_JOB QMGR_JOB; +typedef struct QMGR_PEER QMGR_PEER; +typedef struct QMGR_TRANSPORT_LIST QMGR_TRANSPORT_LIST; +typedef struct QMGR_QUEUE_LIST QMGR_QUEUE_LIST; +typedef struct QMGR_ENTRY_LIST QMGR_ENTRY_LIST; +typedef struct QMGR_JOB_LIST QMGR_JOB_LIST; +typedef struct QMGR_PEER_LIST QMGR_PEER_LIST; +typedef struct QMGR_RCPT QMGR_RCPT; +typedef struct QMGR_RCPT_LIST QMGR_RCPT_LIST; +typedef struct QMGR_SCAN QMGR_SCAN; + + /* + * Hairy macros to update doubly-linked lists. + */ +#define QMGR_LIST_ROTATE(head, object, peers) { \ + head.next->peers.prev = head.prev; \ + head.prev->peers.next = head.next; \ + head.next = object->peers.next; \ + head.next->peers.prev = 0; \ + head.prev = object; \ + object->peers.next = 0; \ +} + +#define QMGR_LIST_UNLINK(head, type, object, peers) { \ + type next = object->peers.next; \ + type prev = object->peers.prev; \ + if (prev) prev->peers.next = next; \ + else head.next = next; \ + if (next) next->peers.prev = prev; \ + else head.prev = prev; \ + object->peers.next = object->peers.prev = 0; \ +} + +#define QMGR_LIST_LINK(head, pred, object, succ, peers) { \ + object->peers.prev = pred; \ + object->peers.next = succ; \ + if (pred) pred->peers.next = object; \ + else head.next = object; \ + if (succ) succ->peers.prev = object; \ + else head.prev = object; \ +} + +#define QMGR_LIST_PREPEND(head, object, peers) { \ + object->peers.next = head.next; \ + object->peers.prev = 0; \ + if (head.next) { \ + head.next->peers.prev = object; \ + } else { \ + head.prev = object; \ + } \ + head.next = object; \ +} + +#define QMGR_LIST_APPEND(head, object, peers) { \ + object->peers.prev = head.prev; \ + object->peers.next = 0; \ + if (head.prev) { \ + head.prev->peers.next = object; \ + } else { \ + head.next = object; \ + } \ + head.prev = object; \ +} + +#define QMGR_LIST_INIT(head) { \ + head.prev = 0; \ + head.next = 0; \ +} + + /* + * Transports are looked up by name (when we have resolved a message), or + * round-robin wise (when we want to distribute resources fairly). + */ +struct QMGR_TRANSPORT_LIST { + QMGR_TRANSPORT *next; + QMGR_TRANSPORT *prev; +}; + +extern struct HTABLE *qmgr_transport_byname; /* transport by name */ +extern QMGR_TRANSPORT_LIST qmgr_transport_list; /* transports, round robin */ + + /* + * Each transport (local, smtp-out, bounce) can have one queue per next hop + * name. Queues are looked up by next hop name (when we have resolved a + * message destination), or round-robin wise (when we want to deliver + * messages fairly). + */ +struct QMGR_QUEUE_LIST { + QMGR_QUEUE *next; + QMGR_QUEUE *prev; +}; + +struct QMGR_JOB_LIST { + QMGR_JOB *next; + QMGR_JOB *prev; +}; + +struct QMGR_TRANSPORT { + int flags; /* blocked, etc. */ + char *name; /* transport name */ + int dest_concurrency_limit; /* concurrency per domain */ + int init_dest_concurrency; /* init. per-domain concurrency */ + int recipient_limit; /* recipients per transaction */ + int rcpt_per_stack; /* extra slots reserved for jobs put + * on the job stack */ + int rcpt_unused; /* available in-core recipient slots */ + int slot_cost; /* cost of new preemption slot (# of + * selected entries) */ + int slot_loan; /* preemption boost offset and */ + int slot_loan_factor; /* factor, see qmgr_job_preempt() */ + int min_slots; /* when preemption can take effect at + * all */ + struct HTABLE *queue_byname; /* queues indexed by domain */ + QMGR_QUEUE_LIST queue_list; /* queues, round robin order */ + struct HTABLE *job_byname; /* jobs indexed by queue id */ + QMGR_JOB_LIST job_list; /* list of message jobs (1 per + * message) ordered by scheduler */ + QMGR_JOB_LIST job_bytime; /* jobs ordered by time since queued */ + QMGR_JOB *job_current; /* keeps track of the current job */ + QMGR_JOB *job_next_unread; /* next job with unread recipients */ + QMGR_JOB *candidate_cache; /* cached result from + * qmgr_job_candidate() */ + QMGR_JOB *candidate_cache_current; /* current job tied to the candidate */ + time_t candidate_cache_time; /* when candidate_cache was last + * updated */ + int blocker_tag; /* for marking blocker jobs */ + QMGR_TRANSPORT_LIST peers; /* linkage */ + char *reason; /* why unavailable */ +}; + +#define QMGR_TRANSPORT_STAT_DEAD (1<<1) +#define QMGR_TRANSPORT_STAT_BUSY (1<<2) + +typedef void (*QMGR_TRANSPORT_ALLOC_NOTIFY) (QMGR_TRANSPORT *, VSTREAM *); +extern QMGR_TRANSPORT *qmgr_transport_select(void); +extern void qmgr_transport_alloc(QMGR_TRANSPORT *, QMGR_TRANSPORT_ALLOC_NOTIFY); +extern void qmgr_transport_throttle(QMGR_TRANSPORT *, const char *); +extern void qmgr_transport_unthrottle(QMGR_TRANSPORT *); +extern QMGR_TRANSPORT *qmgr_transport_create(const char *); +extern QMGR_TRANSPORT *qmgr_transport_find(const char *); + + /* + * Each next hop (e.g., a domain name) has its own queue of pending message + * transactions. The "todo" queue contains messages that are to be delivered + * to this next hop. When a message is elected for transmission, it is moved + * from the "todo" queue to the "busy" queue. Messages are taken from the + * "todo" queue in round-robin order. + */ +struct QMGR_ENTRY_LIST { + QMGR_ENTRY *next; + QMGR_ENTRY *prev; +}; + +struct QMGR_QUEUE { + char *name; /* domain name */ + int todo_refcount; /* queue entries (todo list) */ + int busy_refcount; /* queue entries (busy list) */ + int window; /* slow open algorithm */ + QMGR_TRANSPORT *transport; /* transport linkage */ + QMGR_ENTRY_LIST todo; /* todo queue entries */ + QMGR_ENTRY_LIST busy; /* messages on the wire */ + QMGR_QUEUE_LIST peers; /* neighbor queues */ + char *reason; /* why unavailable */ + int blocker_tag; /* tagged if blocks job list */ +}; + +#define QMGR_QUEUE_TODO 1 /* waiting for service */ +#define QMGR_QUEUE_BUSY 2 /* recipients on the wire */ + +extern int qmgr_queue_count; + +extern QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *, const char *); +extern void qmgr_queue_done(QMGR_QUEUE *); +extern void qmgr_queue_throttle(QMGR_QUEUE *, const char *); +extern void qmgr_queue_unthrottle(QMGR_QUEUE *); +extern QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *, const char *); + + /* + * Structure for a recipient list. Initially, it just contains recipient + * addresses and file offsets. After the address resolver has done its work, + * each recipient is accompanied by a reference to a specific queues (which + * implies a specific transport). This is an extended version of similar + * information maintained by the recipient_list(3) module. + */ +struct QMGR_RCPT { + long offset; /* REC_TYPE_RCPT byte */ + char *address; /* complete address */ + QMGR_QUEUE *queue; /* resolved queue */ +}; + +struct QMGR_RCPT_LIST { + QMGR_RCPT *info; + int len; + int avail; +}; + +extern void qmgr_rcpt_list_init(QMGR_RCPT_LIST *); +extern void qmgr_rcpt_list_add(QMGR_RCPT_LIST *, long, const char *); +extern void qmgr_rcpt_list_free(QMGR_RCPT_LIST *); + + /* + * Structure of one next-hop queue entry. In order to save some copying + * effort we allow multiple recipients per transaction. + */ +struct QMGR_ENTRY { + VSTREAM *stream; /* delivery process */ + QMGR_MESSAGE *message; /* message info */ + QMGR_RCPT_LIST rcpt_list; /* as many as it takes */ + QMGR_QUEUE *queue; /* parent linkage */ + QMGR_PEER *peer; /* parent linkage */ + QMGR_ENTRY_LIST queue_peers; /* per queue neighbor entries */ + QMGR_ENTRY_LIST peer_peers; /* per peer neighbor entries */ +}; + +extern QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *); +extern void qmgr_entry_unselect(QMGR_ENTRY *); +extern void qmgr_entry_done(QMGR_ENTRY *, int); +extern QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *, QMGR_MESSAGE *); + + /* + * All common in-core information about a message is kept here. When all + * recipients have been tried the message file is linked to the "deferred" + * queue (some hosts not reachable), to the "bounce" queue (some recipients + * were rejected), and is then removed from the "active" queue. + */ +struct QMGR_MESSAGE { + int flags; /* delivery problems */ + int qflags; /* queuing flags */ + VSTREAM *fp; /* open queue file or null */ + int refcount; /* queue entries */ + int single_rcpt; /* send one rcpt at a time */ + long arrival_time; /* time when queued */ + time_t queued_time; /* time when moved to the active + * queue */ + long warn_offset; /* warning bounce flag offset */ + time_t warn_time; /* time next warning to be sent */ + long data_offset; /* data seek offset */ + char *queue_name; /* queue name */ + char *queue_id; /* queue file */ + char *sender; /* complete address */ + char *verp_delims; /* VERP delimiters */ + char *errors_to; /* error report address */ + char *return_receipt; /* confirm receipt address */ + char *filter_xport; /* filtering transport */ + char *inspect_xport; /* inspecting transport */ + long data_size; /* message content size */ + long rcpt_offset; /* more recipients here */ + long unread_offset; /* more unread recipients here */ + QMGR_RCPT_LIST rcpt_list; /* complete addresses */ + int rcpt_count; /* used recipient slots */ + int rcpt_limit; /* maximum read in-core */ + int rcpt_unread; /* # of recipients left in queue file */ + QMGR_JOB_LIST job_list; /* jobs delivering this message (1 + * per transport) */ +}; + +#define QMGR_MESSAGE_LOCKED ((QMGR_MESSAGE *) 1) + +extern int qmgr_message_count; +extern int qmgr_recipient_count; +extern MAPS *qmgr_relocated; +extern MAPS *qmgr_virtual; + +extern void qmgr_message_free(QMGR_MESSAGE *); +extern void qmgr_message_update_warn(QMGR_MESSAGE *); +extern QMGR_MESSAGE *qmgr_message_alloc(const char *, const char *, int); +extern QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *); + + /* + * Sometimes it's required to access the transport queues and entries on per + * message basis. That's what the QMGR_JOB structure is for - it groups all + * per message information within each transport using a list of QMGR_PEER + * structures. These structures in turn correspond with per message + * QMGR_QUEUE structure and list all per message QMGR_ENTRY structures. + */ +struct QMGR_PEER_LIST { + QMGR_PEER *next; + QMGR_PEER *prev; +}; + +struct QMGR_JOB { + QMGR_MESSAGE *message; /* message delivered by this job */ + QMGR_TRANSPORT *transport; /* transport this job belongs to */ + QMGR_JOB_LIST message_peers; /* per message neighbor linkage */ + QMGR_JOB_LIST transport_peers; /* per transport neighbor linkage */ + QMGR_JOB_LIST time_peers; /* by time neighbor linkage */ + QMGR_JOB *stack_parent; /* stack parent */ + QMGR_JOB_LIST stack_children; /* all stack children */ + QMGR_JOB_LIST stack_siblings; /* stack children linkage */ + int stack_level; /* job stack nesting level (-1 means + * it's not on the lists at all) */ + int blocker_tag; /* tagged if blocks the job list */ + struct HTABLE *peer_byname; /* message job peers, indexed by + * domain */ + QMGR_PEER_LIST peer_list; /* list of message job peers */ + int slots_used; /* slots used during preemption */ + int slots_available; /* slots available for preemption (in + * multiples of slot_cost) */ + int selected_entries; /* # of entries selected for delivery + * so far */ + int read_entries; /* # of entries read in-core so far */ + int rcpt_count; /* used recipient slots */ + int rcpt_limit; /* available recipient slots */ +}; + +struct QMGR_PEER { + QMGR_JOB *job; /* job handling this peer */ + QMGR_QUEUE *queue; /* queue corresponding with this peer */ + int refcount; /* peer entries */ + QMGR_ENTRY_LIST entry_list; /* todo message entries queued for + * this peer */ + QMGR_PEER_LIST peers; /* neighbor linkage */ +}; + +extern QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *); +extern QMGR_PEER *qmgr_peer_select(QMGR_JOB *); + +extern QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *, QMGR_TRANSPORT *); +extern void qmgr_job_free(QMGR_JOB *); +extern void qmgr_job_move_limits(QMGR_JOB *); + +extern QMGR_PEER *qmgr_peer_create(QMGR_JOB *, QMGR_QUEUE *); +extern QMGR_PEER *qmgr_peer_find(QMGR_JOB *, QMGR_QUEUE *); +extern void qmgr_peer_free(QMGR_PEER *); + + /* + * qmgr_defer.c + */ +extern void qmgr_defer_transport(QMGR_TRANSPORT *, const char *); +extern void qmgr_defer_todo(QMGR_QUEUE *, const char *); +extern void qmgr_defer_recipient(QMGR_MESSAGE *, const char *, const char *); + + /* + * qmgr_bounce.c + */ +extern void PRINTFLIKE(3, 4) qmgr_bounce_recipient(QMGR_MESSAGE *, QMGR_RCPT *, const char *,...); + + /* + * qmgr_deliver.c + */ +extern int qmgr_deliver_concurrency; +extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *); + + /* + * qmgr_active.c + */ +extern int qmgr_active_feed(QMGR_SCAN *, const char *); +extern void qmgr_active_drain(void); +extern void qmgr_active_done(QMGR_MESSAGE *); + + /* + * qmgr_move.c + */ +extern void qmgr_move(const char *, const char *, time_t); + + /* + * qmgr_enable.c + */ +extern void qmgr_enable_all(void); +extern void qmgr_enable_transport(QMGR_TRANSPORT *); +extern void qmgr_enable_queue(QMGR_QUEUE *); + + /* + * Queue scan context. + */ +struct QMGR_SCAN { + char *queue; /* queue name */ + int flags; /* private, this run */ + int nflags; /* private, next run */ + struct SCAN_DIR *handle; /* scan */ +}; + + /* + * Flags that control queue scans or destination selection. These are + * similar to the QMGR_REQ_XXX request codes. + */ +#define QMGR_SCAN_START (1<<0) /* start now/restart when done */ +#define QMGR_SCAN_ALL (1<<1) /* all queue file time stamps */ +#define QMGR_FLUSH_DEAD (1<<2) /* all sites, all transports */ + + /* + * qmgr_scan.c + */ +extern QMGR_SCAN *qmgr_scan_create(const char *); +extern void qmgr_scan_request(QMGR_SCAN *, int); +extern char *qmgr_scan_next(QMGR_SCAN *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_active.c b/gnu/dist/postfix/src/nqmgr/qmgr_active.c new file mode 100644 index 00000000000..722d810739c --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_active.c @@ -0,0 +1,500 @@ +/*++ +/* NAME +/* qmgr_active 3 +/* SUMMARY +/* active queue management +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_active_feed(scan_info, queue_id) +/* QMGR_SCAN *scan_info; +/* const char *queue_id; +/* +/* void qmgr_active_drain() +/* +/* int qmgr_active_done(message) +/* QMGR_MESSAGE *message; +/* DESCRIPTION +/* These functions maintain the active message queue: the set +/* of messages that the queue manager is actually working on. +/* The active queue is limited in size. Messages are drained +/* from the active queue by allocating a delivery process and +/* by delivering mail via that process. Messages leak into the +/* active queue only when the active queue is small enough. +/* Damaged message files are saved to the "corrupt" directory. +/* +/* qmgr_active_feed() inserts the named message file into +/* the active queue. Message files with the wrong name or +/* with other wrong properties are skipped but not removed. +/* The following queue flags are recognized, other flags being +/* ignored: +/* .IP QMGR_SCAN_ALL +/* Examine all queue files. Normally, deferred queue files with +/* future time stamps are ignored, and incoming queue files with +/* future time stamps are frowned upon. +/* .PP +/* qmgr_active_drain() allocates one delivery process. +/* Process allocation is asynchronous. Once the delivery +/* process is available, an attempt is made to deliver +/* a message via it. Message delivery is asynchronous, too. +/* +/* qmgr_active_done() deals with a message after delivery +/* has been tried for all in-core recipients. If the message +/* was bounced, a bounce message is sent to the sender, or +/* to the Errors-To: address if one was specified. +/* If there are more on-file recipients, a new batch of +/* in-core recipients is read from the queue file. Otherwise, +/* if a delivery agent marked the queue file as corrupt, +/* the queue file is moved to the "corrupt" queue (surprise); +/* if at least one delivery failed, the message is moved +/* to the deferred queue. The time stamps of a deferred queue +/* file are set to the nearest wakeup time of its recipient +/* sites (if delivery failed due to a problem with a next-hop +/* host), are set into the future by the amount of time the +/* message was queued (per-message exponential backoff), or are set +/* into the future by a minimal backoff time, whichever is more. +/* The minimal_backoff_time parameter specifies the minimal +/* amount of time between delivery attempts; maximal_backoff_time +/* specifies an upper limit. +/* DIAGNOSTICS +/* Fatal: queue file access failures, out of memory. +/* Panic: interface violations, internal consistency errors. +/* Warnings: corrupt message file. A corrupt message is saved +/* to the "corrupt" queue for further inspection. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <utime.h> +#include <errno.h> + +#ifndef S_IRWXU /* What? no POSIX system? */ +#define S_IRWXU 0700 +#endif + +/* Utility library. */ + +#include <msg.h> +#include <events.h> +#include <mymalloc.h> +#include <vstream.h> + +/* Global library. */ + +#include <mail_params.h> +#include <mail_open_ok.h> +#include <mail_queue.h> +#include <recipient_list.h> +#include <bounce.h> +#include <defer.h> +#include <abounce.h> +#include <rec_type.h> + +/* Application-specific. */ + +#include "qmgr.h" + + /* + * A bunch of call-back routines. + */ +static void qmgr_active_done_2_bounce_flush(int, char *); +static void qmgr_active_done_2_generic(QMGR_MESSAGE *); +static void qmgr_active_done_3_defer_flush(int, char *); +static void qmgr_active_done_3_defer_warn(int, char *); +static void qmgr_active_done_3_generic(QMGR_MESSAGE *); + +/* qmgr_active_corrupt - move corrupted file out of the way */ + +static void qmgr_active_corrupt(const char *queue_id) +{ + char *myname = "qmgr_active_corrupt"; + + if (mail_queue_rename(queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT)) { + if (errno != ENOENT) + msg_fatal("%s: save corrupt file queue %s id %s: %m", + myname, MAIL_QUEUE_ACTIVE, queue_id); + msg_warn("%s: save corrupt file queue %s id %s: %m", + myname, MAIL_QUEUE_ACTIVE, queue_id); + } else { + msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"", + queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT); + } +} + +/* qmgr_active_defer - defer queue file */ + +static void qmgr_active_defer(const char *queue_name, const char *queue_id, + const char *dest_queue, int delay) +{ + 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); + } +} + +/* qmgr_active_feed - feed one message into active queue */ + +int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) +{ + 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. + */ + if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, + scan_info->flags)) == 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); + } +} + +/* qmgr_active_done - dispose of message after recipients have been tried */ + +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->errors_to, + qmgr_active_done_2_bounce_flush, + (char *) message); + else + abounce_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + 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); +} + +/* qmgr_active_done_2_bounce_flush - process abounce_flush() status */ + +static void qmgr_active_done_2_bounce_flush(int status, char *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process abounce_flush() status and continue processing. + */ + message->flags |= status; + qmgr_active_done_2_generic(message); +} + +/* qmgr_active_done_2_generic - continue processing */ + +static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) +{ + char *myname = "qmgr_active_done_2_generic"; + 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; + } + + /* + * 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 + var_max_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->errors_to, + qmgr_active_done_3_defer_flush, + (char *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + 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->errors_to, + qmgr_active_done_3_defer_warn, + (char *) message); + return; + } + } + + /* + * Asynchronous processing does not reach this point. + */ + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_warn - continue after adefer_warn() completion */ + +static void qmgr_active_done_3_defer_warn(int status, char *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_warn() completion status and continue processing. + */ + if (status == 0) + qmgr_message_update_warn(message); + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_defer_flush - continue after adefer_flush() completion */ + +static void qmgr_active_done_3_defer_flush(int status, char *context) +{ + QMGR_MESSAGE *message = (QMGR_MESSAGE *) context; + + /* + * Process adefer_flush() status and continue processing. + */ + message->flags = status; + qmgr_active_done_3_generic(message); +} + +/* qmgr_active_done_3_generic - continue processing */ + +static void qmgr_active_done_3_generic(QMGR_MESSAGE *message) +{ + char *myname = "qmgr_active_done_3_generic"; + int delay; + + /* + * Some recipients need to be tried again. Move the queue file time + * stamps into the future by the amount of time that the message is + * delayed, and move the message to the deferred queue. Impose minimal + * and maximal backoff times. + * + * Since we look at actual time in queue, not time since last delivery + * attempt, backoff times will be distributed. However, we can still see + * spikes in delivery activity because the interval between deferred + * queue scans is finite. + */ + if (message->flags) { + if (message->arrival_time > 0) { + delay = event_time() - message->arrival_time; + if (delay > var_max_backoff_time) + delay = var_max_backoff_time; + if (delay < var_min_backoff_time) + delay = var_min_backoff_time; + } else { + delay = var_min_backoff_time; + } + qmgr_active_defer(message->queue_name, message->queue_id, + MAIL_QUEUE_DEFERRED, delay); + } + + /* + * All recipients done. Remove the queue file. + */ + else { + if (mail_queue_remove(message->queue_name, message->queue_id)) { + if (errno != ENOENT) + msg_fatal("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + msg_warn("%s: remove %s from %s: %m", myname, + message->queue_id, message->queue_name); + } else if (msg_verbose) { + msg_info("%s: remove %s", myname, message->queue_id); + } + } + + /* + * Finally, delete the in-core message structure. + */ + qmgr_message_free(message); +} + +/* qmgr_active_drain - drain active queue by allocating a delivery process */ + +void qmgr_active_drain(void) +{ + QMGR_TRANSPORT *transport; + + /* + * Allocate one delivery process for every transport with pending mail. + * The process allocation completes asynchronously. + */ + while ((transport = qmgr_transport_select()) != 0) { + if (msg_verbose) + msg_info("qmgr_active_drain: allocate %s", transport->name); + qmgr_transport_alloc(transport, qmgr_deliver); + } +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_bounce.c b/gnu/dist/postfix/src/nqmgr/qmgr_bounce.c new file mode 100644 index 00000000000..e8f01405f4d --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_bounce.c @@ -0,0 +1,79 @@ +/*++ +/* NAME +/* qmgr_bounce +/* SUMMARY +/* deal with mail that will not be delivered +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_QUEUE *qmgr_bounce_recipient(message, recipient, format, ...) +/* QMGR_MESSAGE *message; +/* QMGR_RCPT *recipient; +/* const char *format; +/* DESCRIPTION +/* qmgr_bounce_recipient() produces a bounce log record. +/* Once the bounce record is written successfully, the recipient +/* is marked as done. When the bounce record cannot be written, +/* the message structure is updated to reflect that the mail is +/* deferred. +/* +/* Arguments: +/* .IP message +/* Open queue file with the message being bounced. +/* .IP recipient +/* The recipient that will not be delivered. +/* .IP format +/* Free-format text that describes why delivery will not happen. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> + +/* Utility library. */ + +/* Global library. */ + +#include <bounce.h> +#include <deliver_completed.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_bounce_recipient - bounce one message recipient */ + +void qmgr_bounce_recipient(QMGR_MESSAGE *message, QMGR_RCPT *recipient, + const char *format,...) +{ + va_list ap; + int status; + + va_start(ap, format); + status = vbounce_append(BOUNCE_FLAG_KEEP, message->queue_id, + recipient->address, "none", + message->arrival_time, format, ap); + va_end(ap); + + if (status == 0) + deliver_completed(message->fp, recipient->offset); + else + message->flags |= status; +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_defer.c b/gnu/dist/postfix/src/nqmgr/qmgr_defer.c new file mode 100644 index 00000000000..72d30aac927 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_defer.c @@ -0,0 +1,164 @@ +/*++ +/* NAME +/* qmgr_defer +/* SUMMARY +/* deal with mail that must be delivered later +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_defer_recipient(message, address, reason) +/* QMGR_MESSAGE *message; +/* const char *address; +/* const char *reason; +/* +/* void qmgr_defer_todo(queue, reason) +/* QMGR_QUEUE *queue; +/* const char *reason; +/* +/* QMGR_QUEUE *qmgr_defer_transport(transport, reason) +/* QMGR_TRANSPORT *transport; +/* const char *reason; +/* DESCRIPTION +/* qmgr_defer_recipient() defers delivery of the named message to +/* the named recipient. It updates the message structure and writes +/* a log entry. +/* +/* qmgr_defer_todo() iterates over all "todo" deliveries queued for +/* the named site, and calls qmgr_defer_recipient() for each recipient +/* found. Side effects caused by qmgr_entry_done(), qmgr_queue_done(), +/* and by qmgr_active_done(): in-core queue entries will disappear, +/* in-core queues may disappear, in-core and on-disk messages may +/* disappear, bounces may be sent, new in-core queues, queue entries +/* and recipients may appear. +/* +/* qmgr_defer_transport() calls qmgr_defer_todo() for each queue +/* that depends on the named transport. See there for side effects. +/* +/* Arguments: +/* .IP recipient +/* A recipient address; used for logging purposes, and for updating +/* the message-specific \fIdefer\fR log. +/* .IP queue +/* Specifies a queue with delivery requests for a specific next-hop +/* host (or local user). +/* .IP transport +/* Specifies a message delivery transport. +/* .IP reason +/* Free-format text that describes why delivery is deferred; this +/* used for logging purposes, and for updating the message-specific +/* \fIdefer\fR log. +/* BUGS +/* The side effects of calling this routine are quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> + +/* Global library. */ + +#include <defer.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_defer_transport - defer todo entries for named transport */ + +void qmgr_defer_transport(QMGR_TRANSPORT *transport, const char *reason) +{ + char *myname = "qmgr_defer_transport"; + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + /* + * Sanity checks. + */ + if (reason == 0) + msg_panic("%s: null reason", myname); + if (msg_verbose) + msg_info("defer transport %s: %s", transport->name, reason); + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_defer_todo(queue, reason); + } +} + +/* qmgr_defer_todo - defer all todo queue entries for specific site */ + +void qmgr_defer_todo(QMGR_QUEUE *queue, const char *reason) +{ + char *myname = "qmgr_defer_todo"; + QMGR_ENTRY *entry; + QMGR_ENTRY *next; + QMGR_MESSAGE *message; + QMGR_RCPT *recipient; + int nrcpt; + + /* + * Sanity checks. + */ + if (reason == 0) + msg_panic("%s: null reason", myname); + if (msg_verbose) + msg_info("defer site %s: %s", queue->name, reason); + + /* + * Proceed carefully. Queue entries will disappear as a side effect. + */ + for (entry = queue->todo.next; entry != 0; entry = next) { + next = entry->queue_peers.next; + message = entry->message; + for (nrcpt = 0; nrcpt < entry->rcpt_list.len; nrcpt++) { + recipient = entry->rcpt_list.info + nrcpt; + qmgr_defer_recipient(message, recipient->address, reason); + } + qmgr_entry_done(entry, QMGR_QUEUE_TODO); + } +} + +/* qmgr_defer_recipient - defer delivery of specific recipient */ + +void qmgr_defer_recipient(QMGR_MESSAGE *message, const char *address, + const char *reason) +{ + char *myname = "qmgr_defer_recipient"; + + /* + * Sanity checks. + */ + if (reason == 0) + msg_panic("%s: reason 0", myname); + + /* + * Update the message structure and log the message disposition. + */ + message->flags |= defer_append(BOUNCE_FLAG_KEEP, message->queue_id, + address, "none", message->arrival_time, + "%s", reason); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_deliver.c b/gnu/dist/postfix/src/nqmgr/qmgr_deliver.c new file mode 100644 index 00000000000..394b267d6a9 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_deliver.c @@ -0,0 +1,349 @@ +/*++ +/* NAME +/* qmgr_deliver 3 +/* SUMMARY +/* deliver one per-site queue entry to that site +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_deliver_concurrency; +/* +/* int qmgr_deliver(transport, fp) +/* QMGR_TRANSPORT *transport; +/* VSTREAM *fp; +/* DESCRIPTION +/* This module implements the client side of the `queue manager +/* to delivery agent' protocol. The queue manager uses +/* asynchronous I/O so that it can drive multiple delivery +/* agents in parallel. Depending on the outcome of a delivery +/* attempt, the status of messages, queues and transports is +/* updated. +/* +/* qmgr_deliver_concurrency is a global counter that says how +/* many delivery processes are in use. This can be used, for +/* example, to control the size of the `active' message queue. +/* +/* qmgr_deliver() executes when a delivery process announces its +/* availability for the named transport. It arranges for delivery +/* of a suitable queue entry. The \fIfp\fR argument specifies a +/* stream that is connected to the delivery process. Upon completion +/* of delivery (successful or not), the stream is closed, so that the +/* delivery process is released. +/* DIAGNOSTICS +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <time.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <vstring_vstream.h> +#include <events.h> +#include <iostuff.h> + +/* Global library. */ + +#include <mail_queue.h> +#include <mail_proto.h> +#include <recipient_list.h> +#include <mail_params.h> +#include <deliver_request.h> +#include <verp_sender.h> + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_deliver_concurrency; + + /* + * Message delivery status codes. + */ +#define DELIVER_STAT_OK 0 /* all recipients delivered */ +#define DELIVER_STAT_DEFER 1 /* try some recipients later */ +#define DELIVER_STAT_CRASH 2 /* mailer internal problem */ + +/* qmgr_deliver_initial_reply - retrieve initial delivery process response */ + +static int qmgr_deliver_initial_reply(VSTREAM *stream) +{ + int stat; + + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 1) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else { + return (stat ? DELIVER_STAT_DEFER : 0); + } +} + +/* qmgr_deliver_final_reply - retrieve final delivery process response */ + +static int qmgr_deliver_final_reply(VSTREAM *stream, VSTRING *reason) +{ + int stat; + + if (peekfd(vstream_fileno(stream)) < 0) { + msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_WHY, reason, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 2) { + msg_warn("%s: malformed response", VSTREAM_PATH(stream)); + return (DELIVER_STAT_CRASH); + } else { + return (stat ? DELIVER_STAT_DEFER : 0); + } +} + +/* qmgr_deliver_send_request - send delivery request to delivery process */ + +static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) +{ + QMGR_RCPT_LIST list = entry->rcpt_list; + QMGR_RCPT *recipient; + QMGR_MESSAGE *message = entry->message; + char *cp; + VSTRING *sender_buf = 0; + char *sender; + int flags; + char *nexthop; + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info->address); + sender = vstring_str(sender_buf); + } + + /* + * With mail transports that accept only one recipient per delivery, the + * queue name is user@nexthop, so that we can implement per-recipient + * concurrency limits. However, the delivery agent protocol expects + * nexthop only, so we must strip off the recipient local part. + */ + flags = message->inspect_xport ? + DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT; + nexthop = (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? + cp + 1 : entry->queue->name; + attr_print(stream, ATTR_FLAG_MORE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, + ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->data_size, + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_STR, MAIL_ATTR_ERRTO, message->errors_to, + ATTR_TYPE_STR, MAIL_ATTR_RRCPT, message->return_receipt, + ATTR_TYPE_LONG, MAIL_ATTR_TIME, message->arrival_time, + ATTR_TYPE_END); + if (sender_buf != 0) + vstring_free(sender_buf); + for (recipient = list.info; recipient < list.info + list.len; recipient++) + attr_print(stream, ATTR_FLAG_MORE, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, recipient->offset, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient->address, + ATTR_TYPE_END); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_OFFSET, 0, + ATTR_TYPE_END); + if (vstream_fflush(stream) != 0) { + msg_warn("write to process (%s): %m", entry->queue->transport->name); + return (-1); + } else { + if (msg_verbose) + msg_info("qmgr_deliver: site `%s'", entry->queue->name); + return (0); + } +} + +/* qmgr_deliver_abort - transport response watchdog */ + +static void qmgr_deliver_abort(int unused_event, char *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + + msg_fatal("%s: timeout receiving delivery status from transport: %s", + message->queue_id, transport->name); +} + +/* qmgr_deliver_update - process delivery status report */ + +static void qmgr_deliver_update(int unused_event, char *context) +{ + QMGR_ENTRY *entry = (QMGR_ENTRY *) context; + QMGR_QUEUE *queue = entry->queue; + QMGR_TRANSPORT *transport = queue->transport; + QMGR_MESSAGE *message = entry->message; + VSTRING *reason = vstring_alloc(1); + int status; + + /* + * The message transport has responded. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_deliver_abort, context); + + /* + * Retrieve the delivery agent status report. The numerical status code + * indicates if delivery should be tried again. The reason text is sent + * only when a site should be avoided for a while, so that the queue + * manager can log why it does not even try to schedule delivery to the + * affected recipients. + */ + status = qmgr_deliver_final_reply(entry->stream, reason); + + /* + * The mail delivery process failed for some reason (although delivery + * may have been successful). Back off with this transport type for a + * while. Dispose of queue entries for this transport that await + * selection (the todo lists). Stay away from queue entries that have + * been selected (the busy lists), or we would have dangling pointers. + * The queue itself won't go away before we dispose of the current queue + * entry. + */ + if (status == DELIVER_STAT_CRASH) { + message->flags |= DELIVER_STAT_DEFER; + qmgr_transport_throttle(transport, "unknown mail transport error"); + msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description", + transport->name); + qmgr_defer_transport(transport, transport->reason); + } + + /* + * This message must be tried again. + * + * If we have a problem talking to this site, back off with this site for a + * while; dispose of queue entries for this site that await selection + * (the todo list); stay away from queue entries that have been selected + * (the busy list), or we would have dangling pointers. The queue itself + * won't go away before we dispose of the current queue entry. + */ + if (status == DELIVER_STAT_DEFER) { + message->flags |= DELIVER_STAT_DEFER; + if (VSTRING_LEN(reason)) { + qmgr_queue_throttle(queue, vstring_str(reason)); + if (queue->window == 0) + qmgr_defer_todo(queue, queue->reason); + } + } + + /* + * No problems detected. Mark the transport and queue as alive. The queue + * itself won't go away before we dispose of the current queue entry. + */ + if (status == 0) { + qmgr_transport_unthrottle(transport); + qmgr_queue_unthrottle(queue); + } + + /* + * Release the delivery process, and give some other queue entry a chance + * to be delivered. When all recipients for a message have been tried, + * decide what to do next with this message: defer, bounce, delete. + */ + event_disable_readwrite(vstream_fileno(entry->stream)); + if (vstream_fclose(entry->stream) != 0) + msg_warn("qmgr_deliver_update: close delivery stream: %m"); + entry->stream = 0; + qmgr_deliver_concurrency--; + qmgr_entry_done(entry, QMGR_QUEUE_BUSY); + vstring_free(reason); +} + +/* qmgr_deliver - deliver one per-site queue entry */ + +void qmgr_deliver(QMGR_TRANSPORT *transport, VSTREAM *stream) +{ + QMGR_ENTRY *entry; + + /* + * Find out if this delivery process is really available. Once elected, + * the delivery process is supposed to express its happiness. If there is + * a problem, wipe the pending deliveries for this transport. This + * routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (qmgr_deliver_initial_reply(stream) != 0) { + qmgr_transport_throttle(transport, "mail transport unavailable"); + qmgr_defer_transport(transport, transport->reason); + (void) vstream_fclose(stream); + return; + } + + /* + * Find a suitable queue entry. Things may have changed since this + * transport was allocated. If no suitable entry is found, + * unceremoniously disconnect from the delivery process. The delivery + * agent request reading routine is prepared for the queue manager to + * change its mind for no apparent reason. + */ + if ((entry = qmgr_job_entry_select(transport)) == 0) { + (void) vstream_fclose(stream); + return; + } + + /* + * Send the queue file info and recipient info to the delivery process. + * If there is a problem, wipe the pending deliveries for this transport. + * This routine runs in response to an external event, so it does not run + * while some other queue manipulation is happening. + */ + if (qmgr_deliver_send_request(entry, stream) < 0) { + qmgr_entry_unselect(entry); + qmgr_transport_throttle(transport, "mail transport unavailable"); + qmgr_defer_transport(transport, transport->reason); + /* warning: entry may be a dangling pointer here */ + (void) vstream_fclose(stream); + return; + } + + /* + * If we get this far, go wait for the delivery status report. + */ + qmgr_deliver_concurrency++; + entry->stream = stream; + event_enable_read(vstream_fileno(stream), + qmgr_deliver_update, (char *) entry); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_deliver_abort, (char *) entry, var_daemon_timeout); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_enable.c b/gnu/dist/postfix/src/nqmgr/qmgr_enable.c new file mode 100644 index 00000000000..5f0c3841b91 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_enable.c @@ -0,0 +1,107 @@ +/*++ +/* NAME +/* qmgr_enable +/* SUMMARY +/* enable dead transports or sites +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_enable_queue(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_enable_transport(transport) +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_enable_all(void) +/* DESCRIPTION +/* This module purges dead in-core state information, effectively +/* re-enabling delivery. +/* +/* qmgr_enable_queue() enables deliveries to the named dead site. +/* Empty queues are destroyed. The existed solely to indicate that +/* a site is dead. +/* +/* qmgr_enable_transport() enables deliveries via the specified +/* transport, and calls qmgr_enable_queue() for each destination +/* on that transport. Empty queues are destroyed. +/* +/* qmgr_enable_all() enables all transports and queues. +/* See above for the side effects caused by doing this. +/* BUGS +/* The side effects of calling this module can be quite dramatic. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_enable_all - enable transports and queues */ + +void qmgr_enable_all(void) +{ + QMGR_TRANSPORT *xport; + + if (msg_verbose) + msg_info("qmgr_enable_all"); + + /* + * The number of transports does not change as a side effect, so this can + * be a straightforward loop. + */ + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) + qmgr_enable_transport(xport); +} + +/* qmgr_enable_transport - defer todo entries for named transport */ + +void qmgr_enable_transport(QMGR_TRANSPORT *transport) +{ + QMGR_QUEUE *queue; + QMGR_QUEUE *next; + + /* + * Proceed carefully. Queues may disappear as a side effect. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) { + if (msg_verbose) + msg_info("enable transport %s", transport->name); + qmgr_transport_unthrottle(transport); + } + for (queue = transport->queue_list.next; queue; queue = next) { + next = queue->peers.next; + qmgr_enable_queue(queue); + } +} + +/* qmgr_enable_queue - enable and possibly delete queue */ + +void qmgr_enable_queue(QMGR_QUEUE *queue) +{ + if (queue->window == 0) { + if (msg_verbose) + msg_info("enable site %s/%s", queue->transport->name, queue->name); + qmgr_queue_unthrottle(queue); + } + if (queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_entry.c b/gnu/dist/postfix/src/nqmgr/qmgr_entry.c new file mode 100644 index 00000000000..64cec0b28f8 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_entry.c @@ -0,0 +1,268 @@ +/*++ +/* NAME +/* qmgr_entry 3 +/* SUMMARY +/* per-site queue entries +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_ENTRY *qmgr_entry_create(peer, message) +/* QMGR_PEER *peer; +/* QMGR_MESSAGE *message; +/* +/* void qmgr_entry_done(entry, which) +/* QMGR_ENTRY *entry; +/* int which; +/* +/* QMGR_ENTRY *qmgr_entry_select(queue) +/* QMGR_QUEUE *queue; +/* +/* void qmgr_entry_unselect(queue, entry) +/* QMGR_QUEUE *queue; +/* QMGR_ENTRY *entry; +/* DESCRIPTION +/* These routines add/delete/manipulate per-site message +/* delivery requests. +/* +/* qmgr_entry_create() creates an entry for the named peer and message, +/* and appends the entry to the peer's list and its queue's todo list. +/* Filling in and cleaning up the recipients is the responsibility +/* of the caller. +/* +/* qmgr_entry_done() discards a per-site queue entry. The +/* \fIwhich\fR argument is either QMGR_QUEUE_BUSY for an entry +/* of the site's `busy' list (i.e. queue entries that have been +/* selected for actual delivery), or QMGR_QUEUE_TODO for an entry +/* of the site's `todo' list (i.e. queue entries awaiting selection +/* for actual delivery). +/* +/* qmgr_entry_done() discards its peer structure when the peer +/* is not referenced anymore. +/* +/* qmgr_entry_done() triggers cleanup of the per-site queue when +/* the site has no pending deliveries, and the site is either +/* alive, or the site is dead and the number of in-core queues +/* exceeds a configurable limit (see qmgr_queue_done()). +/* +/* qmgr_entry_done() triggers special action when the last in-core +/* queue entry for a message is done with: either read more +/* recipients from the queue file, delete the queue file, or move +/* the queue file to the deferred queue; send bounce reports to the +/* message originator (see qmgr_active_done()). +/* +/* qmgr_entry_select() selects first entry from the named +/* per-site queue's `todo' list for actual delivery. The entry is +/* moved to the queue's `busy' list: the list of messages being +/* delivered. The entry is also removed from its peer list. +/* +/* qmgr_entry_unselect() takes the named entry off the named +/* per-site queue's `busy' list and moves it to the queue's +/* `todo' list. The entry is also appended to its peer list again. +/* DIAGNOSTICS +/* Panic: interface violations, internal inconsistencies. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <events.h> +#include <vstream.h> + +/* Global library. */ + +#include <mail_params.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_entry_select - select queue entry for delivery */ + +QMGR_ENTRY *qmgr_entry_select(QMGR_PEER *peer) +{ + QMGR_ENTRY *entry; + QMGR_QUEUE *queue; + + if ((entry = peer->entry_list.next) != 0) { + queue = entry->queue; + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers); + queue->todo_refcount--; + QMGR_LIST_APPEND(queue->busy, entry, queue_peers); + queue->busy_refcount++; + QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers); + peer->job->selected_entries++; + } + return (entry); +} + +/* qmgr_entry_unselect - unselect queue entry for delivery */ + +void qmgr_entry_unselect(QMGR_ENTRY *entry) +{ + QMGR_PEER *peer = entry->peer; + QMGR_QUEUE *queue = entry->queue; + + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); + queue->busy_refcount--; + QMGR_LIST_APPEND(queue->todo, entry, queue_peers); + queue->todo_refcount++; + QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers); + peer->job->selected_entries--; +} + +/* qmgr_entry_done - dispose of queue entry */ + +void qmgr_entry_done(QMGR_ENTRY *entry, int which) +{ + QMGR_QUEUE *queue = entry->queue; + QMGR_MESSAGE *message = entry->message; + QMGR_PEER *peer = entry->peer; + QMGR_JOB *sponsor, + *job = peer->job; + QMGR_TRANSPORT *transport = job->transport; + + /* + * Take this entry off the in-core queue. + */ + if (entry->stream != 0) + msg_panic("qmgr_entry_done: file is open"); + if (which == QMGR_QUEUE_BUSY) { + QMGR_LIST_UNLINK(queue->busy, QMGR_ENTRY *, entry, queue_peers); + queue->busy_refcount--; + } else if (which == QMGR_QUEUE_TODO) { + QMGR_LIST_UNLINK(peer->entry_list, QMGR_ENTRY *, entry, peer_peers); + job->selected_entries++; + QMGR_LIST_UNLINK(queue->todo, QMGR_ENTRY *, entry, queue_peers); + queue->todo_refcount--; + } else { + msg_panic("qmgr_entry_done: bad queue spec: %d", which); + } + + /* + * Decrease the in-core recipient counts and free the recipient list and + * the structure itself. + */ + job->rcpt_count -= entry->rcpt_list.len; + message->rcpt_count -= entry->rcpt_list.len; + qmgr_recipient_count -= entry->rcpt_list.len; + qmgr_rcpt_list_free(&entry->rcpt_list); + myfree((char *) entry); + + /* + * Make sure that the transport of any retired or finishing job that + * donated recipient slots to this message gets them back first. Then, if + * possible, pass the remaining unused recipient slots to the next job on + * the job list. + */ + for (sponsor = message->job_list.next; sponsor; sponsor = sponsor->message_peers.next) { + if (sponsor->rcpt_count >= sponsor->rcpt_limit || sponsor == job) + continue; + if (sponsor->stack_level < 0 || message->rcpt_offset == 0) + qmgr_job_move_limits(sponsor); + } + if (message->rcpt_offset == 0) { + qmgr_job_move_limits(job); + } + + /* + * If the queue was blocking some of the jobs on the job list, check if + * the concurrency limit has lifted. If there are still some pending + * deliveries, give it a try and unmark all transport blockers at once. + * The qmgr_job_entry_select() will do the rest. In either case make sure + * the queue is not marked as a blocker anymore, with extra handling of + * queues which were declared dead. + * + * Note that changing the blocker status also affects the candidate cache. + * Most of the cases would be automatically recognized by the current job + * change, but we play safe and reset the cache explicitly below. + * + * Keeping the transport blocker tag odd is an easy way to make sure the tag + * never matches jobs that are not explicitly marked as blockers. + */ + if (queue->blocker_tag == transport->blocker_tag) { + if (queue->window > queue->busy_refcount && queue->todo.next != 0) { + transport->blocker_tag += 2; + transport->job_current = transport->job_list.next; + transport->candidate_cache_current = 0; + } + if (queue->window > queue->busy_refcount || queue->window == 0) + queue->blocker_tag = 0; + } + + /* + * When there are no more entries for this peer, discard the peer + * structure. + */ + peer->refcount--; + if (peer->refcount == 0) + qmgr_peer_free(peer); + + /* + * When the in-core queue for this site is empty and when this site is + * not dead, discard the in-core queue. When this site is dead, but the + * number of in-core queues exceeds some threshold, get rid of this + * in-core queue anyway, in order to avoid running out of memory. + */ + if (queue->todo.next == 0 && queue->busy.next == 0) { + if (queue->window == 0 && qmgr_queue_count > 2 * var_qmgr_rcpt_limit) + qmgr_queue_unthrottle(queue); + if (queue->window > 0) + qmgr_queue_done(queue); + } + + /* + * Update the in-core message reference count. When the in-core message + * structure has no more references, dispose of the message. + */ + message->refcount--; + if (message->refcount == 0) + qmgr_active_done(message); +} + +/* qmgr_entry_create - create queue todo entry */ + +QMGR_ENTRY *qmgr_entry_create(QMGR_PEER *peer, QMGR_MESSAGE *message) +{ + QMGR_ENTRY *entry; + QMGR_QUEUE *queue = peer->queue; + + /* + * Sanity check. + */ + if (queue->window == 0) + msg_panic("qmgr_entry_create: dead queue: %s", queue->name); + + entry = (QMGR_ENTRY *) mymalloc(sizeof(QMGR_ENTRY)); + entry->stream = 0; + entry->message = message; + qmgr_rcpt_list_init(&entry->rcpt_list); + message->refcount++; + entry->peer = peer; + QMGR_LIST_APPEND(peer->entry_list, entry, peer_peers); + peer->refcount++; + entry->queue = queue; + QMGR_LIST_APPEND(queue->todo, entry, queue_peers); + queue->todo_refcount++; + return (entry); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_job.c b/gnu/dist/postfix/src/nqmgr/qmgr_job.c new file mode 100644 index 00000000000..094838b77e2 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_job.c @@ -0,0 +1,912 @@ +/*++ +/* NAME +/* qmgr_job 3 +/* SUMMARY +/* per-transport jobs +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_JOB *qmgr_job_obtain(message, transport) +/* QMGR_MESSAGE *message; +/* QMGR_TRANSPORT *transport; +/* +/* void qmgr_job_free(job) +/* QMGR_JOB *job; +/* +/* void qmgr_job_move_limits(job) +/* QMGR_JOB *job; +/* +/* QMGR_ENTRY *qmgr_job_entry_select(transport) +/* QMGR_TRANSPORT *transport; +/* DESCRIPTION +/* These routines add/delete/manipulate per-transport jobs. +/* Each job corresponds to a specific transport and message. +/* Each job has a peer list containing all pending delivery +/* requests for that message. +/* +/* qmgr_job_obtain() finds an existing job for named message and +/* transport combination. New empty job is created if no existing can +/* be found. In either case, the job is prepared for assignement of +/* (more) message recipients. +/* +/* qmgr_job_free() disposes of a per-transport job after all +/* its entries have been taken care of. It is an error to dispose +/* of a job that is still in use. +/* +/* qmgr_job_entry_select() attempts to find the next entry suitable +/* for delivery. The job preempting algorithm is also exercised. +/* If necessary, an attempt to read more recipients into core is made. +/* This can result in creation of more job, queue and entry structures. +/* +/* qmgr_job_move_limits() takes care of proper distribution of the +/* per-transport recipients limit among the per-transport jobs. +/* Should be called whenever a job's recipient slot becomes available. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* patrik@raxoft.cz +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <mymalloc.h> +#include <sane_time.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* Forward declarations */ + +static void qmgr_job_pop(QMGR_JOB *); + +/* Helper macros */ + +#define HAS_ENTRIES(job) ((job)->selected_entries < (job)->read_entries) + +/* + * The MIN_ENTRIES macro may underestimate a lot but we can't use message->rcpt_unread + * because we don't know if all those unread recipients go to our transport yet. + */ + +#define MIN_ENTRIES(job) ((job)->read_entries) +#define MAX_ENTRIES(job) ((job)->read_entries + (job)->message->rcpt_unread) + +#define RESET_CANDIDATE_CACHE(transport) ((transport)->candidate_cache_current = 0) + +#define IS_BLOCKER(job,transport) ((job)->blocker_tag == (transport)->blocker_tag) + +/* qmgr_job_create - create and initialize message job structure */ + +static QMGR_JOB *qmgr_job_create(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job; + + job = (QMGR_JOB *) mymalloc(sizeof(QMGR_JOB)); + job->message = message; + QMGR_LIST_APPEND(message->job_list, job, message_peers); + htable_enter(transport->job_byname, message->queue_id, (char *) job); + job->transport = transport; + QMGR_LIST_INIT(job->transport_peers); + QMGR_LIST_INIT(job->time_peers); + job->stack_parent = 0; + QMGR_LIST_INIT(job->stack_children); + QMGR_LIST_INIT(job->stack_siblings); + job->stack_level = -1; + job->blocker_tag = 0; + job->peer_byname = htable_create(0); + QMGR_LIST_INIT(job->peer_list); + job->slots_used = 0; + job->slots_available = 0; + job->selected_entries = 0; + job->read_entries = 0; + job->rcpt_count = 0; + job->rcpt_limit = 0; + return (job); +} + +/* qmgr_job_link - append the job to the job lists based on the time it was queued */ + +static void qmgr_job_link(QMGR_JOB *job) +{ + QMGR_TRANSPORT *transport = job->transport; + QMGR_MESSAGE *message = job->message; + QMGR_JOB *prev, + *next, + *list_prev, + *list_next, + *unread, + *current; + int delay; + + /* + * Sanity checks. + */ + if (job->stack_level >= 0) + msg_panic("qmgr_job_link: already on the job lists (%d)", job->stack_level); + + /* + * Traverse the time list and the scheduler list from the end and stop + * when we found job older than the one beeing linked. + * + * During the traversals keep track if we have come accross either the + * current job or the first unread job on the job list. If this is the + * case, these pointers will be adjusted below as required. + * + * Although both lists are exactly the same when only jobs on the stack + * level zero are considered, it's easier to traverse them separately. + * Otherwise it's impossible to keep track of the current job pointer + * effectively. + * + * This may look inefficient but under normal operation it is expected that + * the loops will stop right away, resulting in normal list appends + * below. However, this code is necessary for reviving retired jobs and + * for jobs which are created long after the first chunk of recipients + * was read in-core (either of these can happen only for multi-transport + * messages). + */ + current = transport->job_current; + for (next = 0, prev = transport->job_list.prev; prev; + next = prev, prev = prev->transport_peers.prev) { + if (prev->stack_parent == 0) { + delay = message->queued_time - prev->message->queued_time; + if (delay >= 0) + break; + } + if (current == prev) + current = 0; + } + list_prev = prev; + list_next = next; + + unread = transport->job_next_unread; + for (next = 0, prev = transport->job_bytime.prev; prev; + next = prev, prev = prev->time_peers.prev) { + delay = message->queued_time - prev->message->queued_time; + if (delay >= 0) + break; + if (unread == prev) + unread = 0; + } + + /* + * Link the job into the proper place on the job lists and mark it so we + * know it has been linked. + */ + job->stack_level = 0; + QMGR_LIST_LINK(transport->job_list, list_prev, job, list_next, transport_peers); + QMGR_LIST_LINK(transport->job_bytime, prev, job, next, time_peers); + + /* + * Update the current job pointer if necessary. + */ + if (current == 0) + transport->job_current = job; + + /* + * Update the pointer to the first unread job on the job list and steal + * the unused recipient slots from the old one. + */ + if (unread == 0) { + unread = transport->job_next_unread; + transport->job_next_unread = job; + if (unread != 0) + qmgr_job_move_limits(unread); + } + + /* + * Get as much recipient slots as possible. The excess will be returned + * to the transport pool as soon as the exact amount required is known + * (which is usually after all recipients have been read in core). + */ + if (transport->rcpt_unused > 0) { + job->rcpt_limit += transport->rcpt_unused; + message->rcpt_limit += transport->rcpt_unused; + transport->rcpt_unused = 0; + } +} + +/* qmgr_job_find - lookup job associated with named message and transport */ + +static QMGR_JOB *qmgr_job_find(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + + /* + * Instead of traversing the message job list, we use single per + * transport hash table. This is better (at least with respect to memory + * usage) than having single hash table (usually almost empty) for each + * message. + */ + return ((QMGR_JOB *) htable_find(transport->job_byname, message->queue_id)); +} + +/* qmgr_job_obtain - find/create the appropriate job and make it ready for new recipients */ + +QMGR_JOB *qmgr_job_obtain(QMGR_MESSAGE *message, QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job; + + /* + * Try finding an existing job, reviving it if it was already retired. + * Create a new job for this transport/message combination otherwise. In + * either case, the job ends linked on the job lists. + */ + if ((job = qmgr_job_find(message, transport)) == 0) + job = qmgr_job_create(message, transport); + if (job->stack_level < 0) + qmgr_job_link(job); + + /* + * Reset the candidate cache because of the new expected recipients. Make + * sure the job is not marked as a blocker for the same reason. Note that + * this can result in having a non-blocker followed by more blockers. + * Consequently, we can't just update the current job pointer, we have to + * reset it. Fortunately qmgr_job_entry_select() will easily deal with + * this and will lookup the real current job for us. + */ + RESET_CANDIDATE_CACHE(transport); + if (IS_BLOCKER(job, transport)) { + job->blocker_tag = 0; + transport->job_current = transport->job_list.next; + } + return (job); +} + +/* qmgr_job_move_limits - move unused recipient slots to the next unread job */ + +void qmgr_job_move_limits(QMGR_JOB *job) +{ + QMGR_TRANSPORT *transport = job->transport; + QMGR_MESSAGE *message = job->message; + QMGR_JOB *next = transport->job_next_unread; + int rcpt_unused, + msg_rcpt_unused; + + /* + * Find next unread job on the job list if necessary. Cache it for later. + * This makes the amortized efficiency of this routine O(1) per job. Note + * that we use the time list whose ordering doesn't change over time. + */ + if (job == next) { + for (next = next->time_peers.next; next; next = next->time_peers.next) + if (next->message->rcpt_offset != 0) + break; + transport->job_next_unread = next; + } + + /* + * Calculate the number of available unused slots. + */ + rcpt_unused = job->rcpt_limit - job->rcpt_count; + msg_rcpt_unused = message->rcpt_limit - message->rcpt_count; + if (msg_rcpt_unused < rcpt_unused) + rcpt_unused = msg_rcpt_unused; + + /* + * Transfer the unused recipient slots back to the transport pool and to + * the next not-fully-read job. Job's message limits are adjusted + * accordingly. Note that the transport pool can be negative if we used + * some of the rcpt_per_stack slots. + */ + if (rcpt_unused > 0) { + job->rcpt_limit -= rcpt_unused; + message->rcpt_limit -= rcpt_unused; + transport->rcpt_unused += rcpt_unused; + if (next != 0 && (rcpt_unused = transport->rcpt_unused) > 0) { + next->rcpt_limit += rcpt_unused; + next->message->rcpt_limit += rcpt_unused; + transport->rcpt_unused = 0; + } + } +} + +/* qmgr_job_parent_gone - take care of orphaned stack children */ + +static void qmgr_job_parent_gone(QMGR_JOB *job, QMGR_JOB *parent) +{ + QMGR_JOB *child; + + while ((child = job->stack_children.next) != 0) { + QMGR_LIST_UNLINK(job->stack_children, QMGR_JOB *, child, stack_siblings); + if (parent != 0) + QMGR_LIST_APPEND(parent->stack_children, child, stack_siblings); + child->stack_parent = parent; + } +} + +/* qmgr_job_unlink - unlink the job from the job lists */ + +static void qmgr_job_unlink(QMGR_JOB *job) +{ + char *myname = "qmgr_job_unlink"; + QMGR_TRANSPORT *transport = job->transport; + + /* + * Sanity checks. + */ + if (job->stack_level != 0) + msg_panic("%s: non-zero stack level (%d)", myname, job->stack_level); + if (job->stack_parent != 0) + msg_panic("%s: parent present", myname); + if (job->stack_siblings.next != 0) + msg_panic("%s: siblings present", myname); + + /* + * Make sure that children of job on zero stack level are informed that + * their parent is gone too. + */ + qmgr_job_parent_gone(job, 0); + + /* + * Update the current job pointer if necessary. + */ + if (transport->job_current == job) + transport->job_current = job->transport_peers.next; + + /* + * Invalidate the candidate selection cache if necessary. + */ + if (job == transport->candidate_cache + || job == transport->candidate_cache_current) + RESET_CANDIDATE_CACHE(transport); + + /* + * Remove the job from the job lists and mark it as unlinked. + */ + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + QMGR_LIST_UNLINK(transport->job_bytime, QMGR_JOB *, job, time_peers); + job->stack_level = -1; +} + +/* qmgr_job_retire - remove the job from the job lists while waiting for recipients to deliver */ + +static void qmgr_job_retire(QMGR_JOB *job) +{ + if (msg_verbose) + msg_info("qmgr_job_retire: %s", job->message->queue_id); + + /* + * Pop the job from the job stack if necessary. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + + /* + * Make sure this job is not cached as the next unread job for this + * transport. The qmgr_entry_done() will make sure that the slots donated + * by this job are moved back to the transport pool as soon as possible. + */ + qmgr_job_move_limits(job); + + /* + * Remove the job from the job lists. Note that it remains on the message + * job list, though, and that it can be revived by using + * qmgr_job_obtain(). Also note that the available slot counter is left + * intact. + */ + qmgr_job_unlink(job); +} + +/* qmgr_job_free - release the job structure */ + +void qmgr_job_free(QMGR_JOB *job) +{ + char *myname = "qmgr_job_free"; + QMGR_MESSAGE *message = job->message; + QMGR_TRANSPORT *transport = job->transport; + + if (msg_verbose) + msg_info("%s: %s %s", myname, message->queue_id, transport->name); + + /* + * Sanity checks. + */ + if (job->rcpt_count) + msg_panic("%s: non-zero recipient count (%d)", myname, job->rcpt_count); + + /* + * Pop the job from the job stack if necessary. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + + /* + * Return any remaining recipient slots back to the recipient slots pool. + */ + qmgr_job_move_limits(job); + if (job->rcpt_limit) + msg_panic("%s: recipient slots leak (%d)", myname, job->rcpt_limit); + + /* + * Unlink and discard the structure. Check if the job is still linked on + * the job lists or if it was already retired before unlinking it. + */ + if (job->stack_level >= 0) + qmgr_job_unlink(job); + QMGR_LIST_UNLINK(message->job_list, QMGR_JOB *, job, message_peers); + htable_delete(transport->job_byname, message->queue_id, (void (*) (char *)) 0); + htable_free(job->peer_byname, (void (*) (char *)) 0); + myfree((char *) job); +} + +/* qmgr_job_count_slots - maintain the delivery slot counters */ + +static void qmgr_job_count_slots(QMGR_JOB *job) +{ + + /* + * Count the number of delivery slots used during the delivery of the + * selected job. Also count the number of delivery slots available for + * its preemption. + * + * Despite its trivial look, this is one of the key parts of the theory + * behind this preempting scheduler. + */ + job->slots_available++; + job->slots_used++; + + /* + * If the selected job is not the original current job, reset the + * candidate cache because the change above have slightly increased the + * chance of this job becoming a candidate next time. + * + * Don't expect that the change of the current jobs this turn will render + * the candidate cache invalid the next turn - it can happen that the + * next turn the original current job will be selected again and the + * cache would be considered valid in such case. + */ + if (job != job->transport->candidate_cache_current) + RESET_CANDIDATE_CACHE(job->transport); +} + +/* qmgr_job_candidate - find best job candidate for preempting given job */ + +static QMGR_JOB *qmgr_job_candidate(QMGR_JOB *current) +{ + QMGR_TRANSPORT *transport = current->transport; + QMGR_JOB *job, + *best_job = 0; + double score, + best_score = 0.0; + int max_slots, + max_needed_entries, + max_total_entries; + int delay; + time_t now = sane_time(); + + /* + * Fetch the result directly from the cache if the cache is still valid. + * + * Note that we cache negative results too, so the cache must be invalidated + * by resetting the cached current job pointer, not the candidate pointer + * itself. + * + * In case the cache is valid and contains no candidate, we can ignore the + * time change, as it affects only which candidate is the best, not if + * one exists. However, this feature requires that we no longer relax the + * cache resetting rules, depending on the automatic cache timeout. + */ + if (transport->candidate_cache_current == current + && (transport->candidate_cache_time == now + || transport->candidate_cache == 0)) + return (transport->candidate_cache); + + /* + * Estimate the minimum amount of delivery slots that can ever be + * accumulated for the given job. All jobs that won't fit into these + * slots are excluded from the candidate selection. + */ + max_slots = (MIN_ENTRIES(current) - current->selected_entries + + current->slots_available) / transport->slot_cost; + + /* + * Select the candidate with best time_since_queued/total_recipients + * score. In addition to jobs which don't meet the max_slots limit, skip + * also jobs which don't have any selectable entries at the moment. + * + * Instead of traversing the whole job list we traverse it just from the + * current job forward. This has several advantages. First, we skip some + * of the blocker jobs and the current job itself right away. But the + * really important advantage is that we are sure that we don't consider + * any jobs that are already stack children of the current job. Thanks to + * this we can easily include all encountered jobs which are leaf + * children of some of the preempting stacks as valid candidates. All we + * need to do is to make sure we do not include any of the stack parents. + * And, because the leaf children are not ordered by the time since + * queued, we have to exclude them from the early loop end test. + * + * However, don't bother searching if we can't find anything suitable + * anyway. + */ + if (max_slots > 0) { + for (job = current->transport_peers.next; job; job = job->transport_peers.next) { + if (job->stack_children.next != 0 || IS_BLOCKER(job, transport)) + continue; + max_total_entries = MAX_ENTRIES(job); + max_needed_entries = max_total_entries - job->selected_entries; + delay = now - job->message->queued_time + 1; + if (max_needed_entries > 0 && max_needed_entries <= max_slots) { + score = (double) delay / max_total_entries; + if (score > best_score) { + best_score = score; + best_job = job; + } + } + + /* + * Stop early if the best score is as good as it can get. + */ + if (delay <= best_score && job->stack_level == 0) + break; + } + } + + /* + * Cache the result for later use. + */ + transport->candidate_cache = best_job; + transport->candidate_cache_current = current; + transport->candidate_cache_time = now; + + return (best_job); +} + +/* qmgr_job_preempt - preempt large message with smaller one */ + +static QMGR_JOB *qmgr_job_preempt(QMGR_JOB *current) +{ + char *myname = "qmgr_job_preempt"; + QMGR_TRANSPORT *transport = current->transport; + QMGR_JOB *job, + *prev; + int expected_slots; + int rcpt_slots; + + /* + * Suppress preempting completely if the current job is not big enough to + * accumulate even the mimimal number of slots required. + * + * Also, don't look for better job candidate if there are no available slots + * yet (the count can get negative due to the slot loans below). + */ + if (current->slots_available <= 0 + || MAX_ENTRIES(current) < transport->min_slots * transport->slot_cost) + return (current); + + /* + * Find best candidate for preempting the current job. + * + * Note that the function also takes care that the candidate fits within the + * number of delivery slots which the current job is still able to + * accumulate. + */ + if ((job = qmgr_job_candidate(current)) == 0) + return (current); + + /* + * Sanity checks. + */ + if (job == current) + msg_panic("%s: attempt to preempt itself", myname); + if (job->stack_children.next != 0) + msg_panic("%s: already on the job stack (%d)", myname, job->stack_level); + if (job->stack_level < 0) + msg_panic("%s: not on the job list (%d)", myname, job->stack_level); + + /* + * Check if there is enough available delivery slots accumulated to + * preempt the current job. + * + * The slot loaning scheme improves the average message response time. Note + * that the loan only allows the preemption happen earlier, though. It + * doesn't affect how many slots have to be "paid" - in either case the + * full number of slots required has to be accumulated later before the + * current job can be preempted again. + */ + expected_slots = MAX_ENTRIES(job) - job->selected_entries; + if (current->slots_available / transport->slot_cost + transport->slot_loan + < expected_slots * transport->slot_loan_factor / 100.0) + return (current); + + /* + * Preempt the current job. + * + * This involves placing the selected candidate in front of the current job + * on the job list and updating the stack parent/child/sibling pointers + * appropriately. But first we need to make sure that the candidate is + * taken from its previous job stack which it might be top of. + */ + if (job->stack_level > 0) + qmgr_job_pop(job); + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + prev = current->transport_peers.prev; + QMGR_LIST_LINK(transport->job_list, prev, job, current, transport_peers); + job->stack_parent = current; + QMGR_LIST_APPEND(current->stack_children, job, stack_siblings); + job->stack_level = current->stack_level + 1; + + /* + * Update the current job pointer and explicitly reset the candidate + * cache. + */ + transport->job_current = job; + RESET_CANDIDATE_CACHE(transport); + + /* + * Since the single job can be preempted by several jobs at the same + * time, we have to adjust the available slot count now to prevent using + * the same slots multiple times. To do that we subtract the number of + * slots the preempting job will supposedly use. This number will be + * corrected later when that job is popped from the stack to reflect the + * number of slots really used. + * + * As long as we don't need to keep track of how many slots were really + * used, we can (ab)use the slots_used counter for counting the + * difference between the real and expected amounts instead of the + * absolute amount. + */ + current->slots_available -= expected_slots * transport->slot_cost; + job->slots_used = -expected_slots; + + /* + * Add part of extra recipient slots reserved for preempting jobs to the + * new current job if necessary. + * + * Note that transport->rcpt_unused is within <-rcpt_per_stack,0> in such + * case. + */ + if (job->message->rcpt_offset != 0) { + rcpt_slots = (transport->rcpt_per_stack + transport->rcpt_unused + 1) / 2; + job->rcpt_limit += rcpt_slots; + job->message->rcpt_limit += rcpt_slots; + transport->rcpt_unused -= rcpt_slots; + } + if (msg_verbose) + msg_info("%s: %s by %s, level %d", myname, current->message->queue_id, + job->message->queue_id, job->stack_level); + + return (job); +} + +/* qmgr_job_pop - remove the job from its job preemption stack */ + +static void qmgr_job_pop(QMGR_JOB *job) +{ + char *myname = "qmgr_job_pop"; + QMGR_TRANSPORT *transport = job->transport; + QMGR_JOB *parent; + + if (msg_verbose) + msg_info("%s: %s", myname, job->message->queue_id); + + /* + * Sanity checks. + */ + if (job->stack_level <= 0) + msg_panic("%s: not on the job stack (%d)", myname, job->stack_level); + + /* + * Adjust the number of delivery slots available to preempt job's parent. + * + * Note that we intentionally do not adjust slots_used of the parent. Doing + * so would decrease the maximum per message inflation factor if the + * preemption appeared near the end of parent delivery. + * + * For the same reason we do not adjust parent's slots_available if the + * parent is not the original parent that was preempted by this job + * (i.e., the original parent job has already completed). + * + * This is another key part of the theory behind this preempting scheduler. + */ + if ((parent = job->stack_parent) != 0 + && job->stack_level == parent->stack_level + 1) + parent->slots_available -= job->slots_used * transport->slot_cost; + + /* + * Remove the job from its parent's children list. + */ + if (parent != 0) { + QMGR_LIST_UNLINK(parent->stack_children, QMGR_JOB *, job, stack_siblings); + job->stack_parent = 0; + } + + /* + * If there is a parent, let it adopt all those orphaned children. + * Otherwise at least notify the children that their parent is gone. + */ + qmgr_job_parent_gone(job, parent); + + /* + * Put the job back to stack level zero. + */ + job->stack_level = 0; + + /* + * Explicitely reset the candidate cache. It's not worth trying to skip + * this under some complicated conditions - in most cases the popped job + * is the current job so we would have to reset it anyway. + */ + RESET_CANDIDATE_CACHE(transport); + + /* + * Here we leave the remaining work involving the proper placement on the + * job list to the caller. The most important reason for this is that it + * allows us not to look up where exactly to place the job. + * + * The caller is also made responsible for invalidating the current job + * cache if necessary. + */ +#if 0 + QMGR_LIST_UNLINK(transport->job_list, QMGR_JOB *, job, transport_peers); + QMGR_LIST_LINK(transport->job_list, some_prev, job, some_next, transport_peers); + + if (transport->job_current == job) + transport->job_current = job->transport_peers.next; +#endif +} + +/* qmgr_job_peer_select - select next peer suitable for delivery */ + +static QMGR_PEER *qmgr_job_peer_select(QMGR_JOB *job) +{ + QMGR_PEER *peer; + QMGR_MESSAGE *message = job->message; + + if (HAS_ENTRIES(job) && (peer = qmgr_peer_select(job)) != 0) + return (peer); + + /* + * Try reading in more recipients. Note that we do not try to read them + * as soon as possible as that would decrease the chance of per-site + * recipient grouping. We waited until reading more is really necessary. + * + * XXX Workaround for logic mismatch. The message->refcount test needs + * explanation. If the refcount is zero, it means that qmgr_active_done() + * is beeing completed asynchronously. In such case, we can't read in + * more recipients as bad things would happen after qmgr_active_done() + * continues processing. Note that this results in the given job beeing + * stalled for some time, but fortunately this particular situation is so + * rare that it is not critical. Still we seek for better solution. + */ + if (message->rcpt_offset != 0 + && message->rcpt_limit > message->rcpt_count + && message->refcount > 0) { + qmgr_message_realloc(message); + if (HAS_ENTRIES(job)) + return (qmgr_peer_select(job)); + } + return (0); +} + +/* qmgr_job_entry_select - select next entry suitable for delivery */ + +QMGR_ENTRY *qmgr_job_entry_select(QMGR_TRANSPORT *transport) +{ + QMGR_JOB *job, + *next; + QMGR_PEER *peer; + QMGR_ENTRY *entry; + + /* + * Get the current job if there is one. + */ + if ((job = transport->job_current) == 0) + return (0); + + /* + * Exercise the preempting algorithm if enabled. + * + * The slot_cost equal to 1 causes the algorithm to degenerate and is + * therefore disabled too. + */ + if (transport->slot_cost >= 2) + job = qmgr_job_preempt(job); + + /* + * Select next entry suitable for delivery. In case the current job can't + * provide one because of the per-destination concurrency limits, we mark + * it as a "blocker" job and continue with the next job on the job list. + * + * Note that the loop also takes care of getting the "stall" jobs (job with + * no entries currently available) out of the way if necessary. Stall + * jobs can appear in case of multi-transport messages whose recipients + * don't fit in-core at once. Some jobs created by such message may have + * only few recipients and would stay on the job list until all other + * jobs of that message are delivered, blocking precious recipient slots + * available to this transport. Or it can happen that the job has some + * more entries but suddenly they all get deferred. Whatever the reason, + * we retire such jobs below if we happen to come across some. + */ + for ( /* empty */ ; job; job = next) { + next = job->transport_peers.next; + + /* + * Don't bother if the job is known to have no available entries + * because of the per-destination concurrency limits. + */ + if (IS_BLOCKER(job, transport)) + continue; + + if ((peer = qmgr_job_peer_select(job)) != 0) { + + /* + * We have found a suitable peer. Select one of its entries and + * adjust the delivery slot counters. + */ + entry = qmgr_entry_select(peer); + qmgr_job_count_slots(job); + + /* + * Remember the current job for the next time so we don't have to + * crawl over all those blockers again. They will be reconsidered + * when the concurrency limit permits. + */ + transport->job_current = job; + + /* + * In case we selected the very last job entry, remove the job + * from the job lists right now. + * + * This action uses the assumption that once the job entry has been + * selected, it can be unselected only before the message ifself + * is deferred. Thus the job with all entries selected can't + * re-appear with more entries available for selection again + * (without reading in more entries from the queue file, which in + * turn invokes qmgr_job_obtain() which re-links the job back on + * the lists if necessary). + * + * Note that qmgr_job_move_limits() transfers the recipients slots + * correctly even if the job is unlinked from the job list thanks + * to the job_next_unread caching. + */ + if (!HAS_ENTRIES(job) && job->message->rcpt_offset == 0) + qmgr_job_retire(job); + + /* + * Finally. Hand back the fruit of our tedious effort. + */ + return (entry); + } else if (HAS_ENTRIES(job)) { + + /* + * The job can't be selected due the concurrency limits. Mark it + * together with its queues so we know they are blocking the job + * list and they get the appropriate treatment. In particular, + * all blockers will be reconsidered when one of the problematic + * queues will accept more deliveries. And the job itself will be + * reconsidered if it is assigned some more entries. + */ + job->blocker_tag = transport->blocker_tag; + for (peer = job->peer_list.next; peer; peer = peer->peers.next) + if (peer->entry_list.next != 0) + peer->queue->blocker_tag = transport->blocker_tag; + } else { + + /* + * The job is "stalled". Retire it until it either gets freed or + * gets more entries later. + */ + qmgr_job_retire(job); + } + } + + /* + * We have not found any entry we could use for delivery. Well, things + * must have changed since this transport was selected for asynchronous + * allocation. Never mind. Clear the current job pointer and reluctantly + * report back that we have failed in our task. + */ + transport->job_current = 0; + return (0); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_message.c b/gnu/dist/postfix/src/nqmgr/qmgr_message.c new file mode 100644 index 00000000000..f83f7e0d105 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_message.c @@ -0,0 +1,1025 @@ +/*++ +/* NAME +/* qmgr_message 3 +/* SUMMARY +/* in-core message structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_message_count; +/* int qmgr_recipient_count; +/* +/* QMGR_MESSAGE *qmgr_message_alloc(class, name, qflags) +/* const char *class; +/* const char *name; +/* int qflags; +/* +/* QMGR_MESSAGE *qmgr_message_realloc(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_free(message) +/* QMGR_MESSAGE *message; +/* +/* void qmgr_message_update_warn(message) +/* QMGR_MESSAGE *message; +/* DESCRIPTION +/* This module performs en-gross operations on queue messages. +/* +/* qmgr_message_count is a global counter for the total number +/* of in-core message structures (i.e. the total size of the +/* `active' message queue). +/* +/* qmgr_recipient_count is a global counter for the total number +/* of in-core recipient structures (i.e. the sum of all recipients +/* in all in-core message structures). +/* +/* qmgr_message_alloc() creates an in-core message structure +/* with sender and recipient information taken from the named queue +/* file. A null result means the queue file could not be read or +/* that the queue file contained incorrect information. A result +/* QMGR_MESSAGE_LOCKED means delivery must be deferred. The number +/* of recipients read from a queue file is limited by the global +/* var_qmgr_rcpt_limit configuration parameter. When the limit +/* is reached, the \fIrcpt_offset\fR structure member is set to +/* the position where the read was terminated. Recipients are +/* run through the resolver, and are assigned to destination +/* queues. Recipients that cannot be assigned are deferred or +/* bounced. Mail that has bounced twice is silently absorbed. +/* +/* qmgr_message_realloc() resumes reading recipients from the queue +/* file, and updates the recipient list and \fIrcpt_offset\fR message +/* structure members. A null result means that the file could not be +/* read or that the file contained incorrect information. Recipient +/* limit imposed this time is based on the position of the message +/* job(s) on corresponding transport job list(s). It's considered +/* an error to call this when the recipient slots can't be allocated. +/* +/* qmgr_message_free() destroys an in-core message structure and makes +/* the resources available for reuse. It is an error to destroy +/* a message structure that is still referenced by queue entry structures. +/* +/* qmgr_message_update_warn() takes a closed message, opens it, updates +/* the warning field, and closes it again. +/* DIAGNOSTICS +/* Warnings: malformed message file. Fatal errors: out of memory. +/* SEE ALSO +/* envelope(3) message envelope parser +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <stdio.h> /* sscanf() */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <split_at.h> +#include <valid_hostname.h> +#include <argv.h> +#include <stringops.h> +#include <myflock.h> +#include <sane_time.h> + +/* Global library. */ + +#include <dict.h> +#include <mail_queue.h> +#include <mail_params.h> +#include <canon_addr.h> +#include <record.h> +#include <rec_type.h> +#include <sent.h> +#include <deliver_completed.h> +#include <mail_addr_find.h> +#include <opened.h> +#include <resolve_local.h> +#include <verp_sender.h> + +/* Client stubs. */ + +#include <resolve_clnt.h> + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_message_count; +int qmgr_recipient_count; + +/* qmgr_message_create - create in-core message structure */ + +static QMGR_MESSAGE *qmgr_message_create(const char *queue_name, + const char *queue_id, int qflags) +{ + QMGR_MESSAGE *message; + + message = (QMGR_MESSAGE *) mymalloc(sizeof(QMGR_MESSAGE)); + qmgr_message_count++; + message->flags = 0; + message->qflags = qflags; + message->fp = 0; + message->refcount = 0; + message->single_rcpt = 0; + message->arrival_time = 0; + message->queued_time = sane_time(); + message->data_offset = 0; + message->queue_id = mystrdup(queue_id); + message->queue_name = mystrdup(queue_name); + message->sender = 0; + message->errors_to = 0; + message->return_receipt = 0; + message->filter_xport = 0; + message->inspect_xport = 0; + message->data_size = 0; + message->warn_offset = 0; + message->warn_time = 0; + message->rcpt_offset = 0; + message->verp_delims = 0; + message->unread_offset = 0; + qmgr_rcpt_list_init(&message->rcpt_list); + message->rcpt_count = 0; + message->rcpt_limit = var_qmgr_msg_rcpt_limit; + message->rcpt_unread = 0; + QMGR_LIST_INIT(message->job_list); + return (message); +} + +/* qmgr_message_close - close queue file */ + +static void qmgr_message_close(QMGR_MESSAGE *message) +{ + vstream_fclose(message->fp); + message->fp = 0; +} + +/* qmgr_message_open - open queue file */ + +static int qmgr_message_open(QMGR_MESSAGE *message) +{ + + /* + * Sanity check. + */ + if (message->fp) + msg_panic("%s: queue file is open", message->queue_id); + + /* + * Open this queue file. Skip files that we cannot open. Back off when + * the system appears to be running out of resources. + */ + if ((message->fp = mail_queue_open(message->queue_name, + message->queue_id, + O_RDWR, 0)) == 0) { + if (errno != ENOENT) + msg_fatal("open %s %s: %m", message->queue_name, message->queue_id); + msg_warn("open %s %s: %m", message->queue_name, message->queue_id); + return (-1); + } + return (0); +} + +/* qmgr_message_oldstyle_scan - extract required information from an old style queue file */ + +static void qmgr_message_oldstyle_scan(QMGR_MESSAGE *message) +{ + VSTRING *buf; + long orig_offset, + curr_offset, + extra_offset; + int rec_type; + char *start; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + if ((orig_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Rewind to the very beginning to make sure we see all records. + */ + if (vstream_fseek(message->fp, 0, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + + /* + * Scan through the old style queue file. Count the total number of + * recipients and find the data/extra sections offsets. Note that the new + * queue files require that data_size equals extra_offset - data_offset, + * so we set data_size to this as well and ignore the size record itself + * completely. + */ + message->rcpt_unread = 0; + do { + if ((curr_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + rec_type = rec_get(message->fp, buf, 0); + start = vstring_str(buf); + if (rec_type == REC_TYPE_DONE || rec_type == REC_TYPE_RCPT) { + message->rcpt_unread++; + } else if (rec_type == REC_TYPE_MESG) { + if ((message->data_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if ((extra_offset = atol(start)) <= curr_offset) + msg_fatal("bad extra offset %s file %s", + start, VSTREAM_PATH(message->fp)); + if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->data_size = extra_offset - message->data_offset; + } + } while (rec_type > 0 && rec_type != REC_TYPE_END); + + /* + * Clean up. + */ + if (vstream_fseek(message->fp, orig_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (message->data_offset == 0 || rec_type != REC_TYPE_END) + msg_fatal("%s: envelope records out of order", message->queue_id); +} + +/* qmgr_message_read - read envelope records */ + +static int qmgr_message_read(QMGR_MESSAGE *message) +{ + VSTRING *buf; + long extra_offset; + int rec_type; + long curr_offset; + long save_offset = message->rcpt_offset; /* save a flag */ + char *start; + int recipient_limit; + + /* + * Initialize. No early returns or we have a memory leak. + */ + buf = vstring_alloc(100); + + /* + * If we re-open this file, skip over on-file recipient records that we + * already looked at, and refill the in-core recipient address list. + * + * For the first time, the message recipient limit is calculated from the + * global recipient limit. This is to avoid reading little recipients + * when the active queue is near empty. When the queue becomes full, only + * the necessary amount is read in core. Such priming is necessary + * because there are no message jobs yet. + * + * For the next time, the recipient limit is based solely on the message + * jobs' positions in the job lists and/or job stacks. + */ + if (message->rcpt_offset) { + if (message->rcpt_list.len) + msg_panic("%s: recipient list not empty on recipient reload", message->queue_id); + if (vstream_fseek(message->fp, message->rcpt_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + message->rcpt_offset = 0; + recipient_limit = message->rcpt_limit - message->rcpt_count; + } else { + recipient_limit = var_qmgr_rcpt_limit - qmgr_recipient_count; + if (recipient_limit < message->rcpt_limit) + recipient_limit = message->rcpt_limit; + } + if (recipient_limit <= 0) + msg_panic("%s: no recipient slots available", message->queue_id); + + /* + * Read envelope records. XXX Rely on the front-end programs to enforce + * record size limits. Read up to recipient_limit recipients from the + * queue file, to protect against memory exhaustion. Recipient records + * may appear before or after the message content, so we keep reading + * from the queue file until we have enough recipients (rcpt_offset != 0) + * and until we know where the message content starts (data_offset != 0). + * + * Note that the total recipient count record is accurate only for fresh + * queue files. After some of the recipients are marked as done and the + * queue file is deferred, it can be used as upper bound estimate only. + * Fortunately, this poses no major problem on the scheduling algorithm, + * as the only impact is that the already deferred messages are not + * chosen by qmgr_job_candidate() as often as they could. + */ + do { + if ((curr_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", VSTREAM_PATH(message->fp)); + if (curr_offset == message->data_offset && curr_offset > 0) { + extra_offset = curr_offset + message->data_size; + if (extra_offset <= curr_offset) + msg_fatal("bad extra offset %ld file %s", + extra_offset, VSTREAM_PATH(message->fp)); + if (vstream_fseek(message->fp, extra_offset, SEEK_SET) < 0) + msg_fatal("seek file %s: %m", VSTREAM_PATH(message->fp)); + curr_offset = extra_offset; + } + rec_type = rec_get(message->fp, buf, 0); + start = vstring_str(buf); + if (rec_type == REC_TYPE_SIZE) { + if (message->data_size == 0) { + switch (sscanf(start, "%ld %ld %d", &message->data_size, + &message->data_offset, &message->rcpt_unread)) { + case 1: + + /* + * Gather data_size, data_offset and rcpt_unread values + * from the old style queue file. + */ + qmgr_message_oldstyle_scan(message); + break; + case 3: + + /* + * No extra work for new style queue files. + */ + break; + default: + msg_fatal("%s: weird size record", message->queue_id); + break; + } + } + } else if (rec_type == REC_TYPE_TIME) { + if (message->arrival_time == 0) + message->arrival_time = atol(start); + } else if (rec_type == REC_TYPE_FILT) { + if (message->filter_xport == 0) + message->filter_xport = mystrdup(start); + } else if (rec_type == REC_TYPE_INSP) { + if (message->inspect_xport == 0) + message->inspect_xport = mystrdup(start); + } else if (rec_type == REC_TYPE_FROM) { + if (message->sender == 0) { + message->sender = mystrdup(start); + opened(message->queue_id, message->sender, + message->data_size, message->rcpt_unread, + "queue %s", message->queue_name); + } + } else if (rec_type == REC_TYPE_DONE) { + if (curr_offset > message->unread_offset) { + message->unread_offset = curr_offset; + message->rcpt_unread--; + } + } else if (rec_type == REC_TYPE_RCPT) { + if (message->rcpt_list.len < recipient_limit) { + message->rcpt_unread--; + qmgr_rcpt_list_add(&message->rcpt_list, curr_offset, start); + if (message->rcpt_list.len >= recipient_limit) { + if ((message->rcpt_offset = vstream_ftell(message->fp)) < 0) + msg_fatal("vstream_ftell %s: %m", + VSTREAM_PATH(message->fp)); + if (message->data_offset != 0 + && message->errors_to != 0 + && message->return_receipt != 0) + break; + } + } + } else if (rec_type == REC_TYPE_ERTO) { + if (message->errors_to == 0) { + message->errors_to = mystrdup(start); + if (message->data_offset != 0 + && message->rcpt_offset != 0 + && message->return_receipt != 0) + break; + } + } else if (rec_type == REC_TYPE_RRTO) { + if (message->return_receipt == 0) { + message->return_receipt = mystrdup(start); + if (message->data_offset != 0 + && message->rcpt_offset != 0 + && message->errors_to != 0) + break; + } + } else if (rec_type == REC_TYPE_WARN) { + if (message->warn_offset == 0) { + message->warn_offset = curr_offset; + message->warn_time = atol(start); + } + } else if (rec_type == REC_TYPE_VERP) { + if (message->verp_delims == 0) { + if (verp_delims_verify(start) != 0) { + msg_warn("%s: bad VERP record content: \"%s\"", + message->queue_id, start); + } else { + message->single_rcpt = 1; + message->verp_delims = mystrdup(start); + } + } + } + } while (rec_type > 0 && rec_type != REC_TYPE_END); + + /* + * Avoid clumsiness elsewhere in the program. When sending data across an + * IPC channel, sending an empty string is more convenient than sending a + * null pointer. + */ + if (message->errors_to == 0) + message->errors_to = mystrdup(""); + if (message->return_receipt == 0) + message->return_receipt = mystrdup(""); + + /* + * Clean up. + */ + vstring_free(buf); + + /* + * Sanity checks. Verify that all required information was found, + * including the queue file end marker. + */ + if (message->rcpt_unread < 0 + || (message->rcpt_offset == 0 && message->rcpt_unread != 0)) { + msg_warn("%s: rcpt count mismatch (%d)", + message->queue_id, message->rcpt_unread); + message->rcpt_unread = 0; + } + if (message->arrival_time == 0 + || message->sender == 0 + || message->data_offset == 0 + || (message->rcpt_offset == 0 && rec_type != REC_TYPE_END)) { + msg_warn("%s: envelope records out of order", message->queue_id); + message->rcpt_offset = save_offset; /* restore flag */ + message->rcpt_unread += message->rcpt_list.len; + qmgr_rcpt_list_free(&message->rcpt_list); + qmgr_rcpt_list_init(&message->rcpt_list); + return (-1); + } else { + return (0); + } +} + +/* qmgr_message_update_warn - update the time of next delay warning */ + +void qmgr_message_update_warn(QMGR_MESSAGE *message) +{ + + /* + * XXX eventually this should let us schedule multiple warnings, right + * now it just allows for one. + */ + if (qmgr_message_open(message) + || vstream_fseek(message->fp, message->warn_offset, SEEK_SET) < 0 + || rec_fprintf(message->fp, REC_TYPE_WARN, REC_TYPE_WARN_FORMAT, 0L) < 0 + || vstream_fflush(message->fp)) + msg_fatal("update queue file %s: %m", VSTREAM_PATH(message->fp)); + qmgr_message_close(message); +} + +/* qmgr_message_sort_compare - compare recipient information */ + +static int qmgr_message_sort_compare(const void *p1, const void *p2) +{ + QMGR_RCPT *rcpt1 = (QMGR_RCPT *) p1; + QMGR_RCPT *rcpt2 = (QMGR_RCPT *) p2; + QMGR_QUEUE *queue1; + QMGR_QUEUE *queue2; + char *at1; + char *at2; + int result; + + /* + * Compare most significant to least significant recipient attributes. + */ + if ((queue1 = rcpt1->queue) != 0 && (queue2 = rcpt2->queue) != 0) { + + /* + * Compare message transport. + */ + if ((result = strcmp(queue1->transport->name, + queue2->transport->name)) != 0) + return (result); + + /* + * Compare (already lowercased) next-hop hostname. + */ + if ((result = strcmp(queue1->name, queue2->name)) != 0) + return (result); + } + + /* + * Compare recipient domain. + */ + if ((at1 = strrchr(rcpt1->address, '@')) != 0 + && (at2 = strrchr(rcpt2->address, '@')) != 0 + && (result = strcasecmp(at1, at2)) != 0) + return (result); + + /* + * Compare recipient address. + */ + return (strcmp(rcpt1->address, rcpt2->address)); +} + +/* qmgr_message_sort - sort message recipient addresses by domain */ + +static void qmgr_message_sort(QMGR_MESSAGE *message) +{ + qsort((char *) message->rcpt_list.info, message->rcpt_list.len, + sizeof(message->rcpt_list.info[0]), qmgr_message_sort_compare); + if (msg_verbose) { + QMGR_RCPT_LIST list = message->rcpt_list; + QMGR_RCPT *rcpt; + + msg_info("start sorted recipient list"); + for (rcpt = list.info; rcpt < list.info + list.len; rcpt++) + msg_info("qmgr_message_sort: %s", rcpt->address); + msg_info("end sorted recipient list"); + } +} + +/* qmgr_message_resolve - resolve recipients */ + +static void qmgr_message_resolve(QMGR_MESSAGE *message) +{ + static ARGV *defer_xport_argv; + QMGR_RCPT_LIST list = message->rcpt_list; + QMGR_RCPT *recipient; + QMGR_TRANSPORT *transport = 0; + QMGR_QUEUE *queue = 0; + RESOLVE_REPLY reply; + const char *newloc; + char *at; + char **cpp; + char *domain; + const char *junk; + char *nexthop; + int len; + +#define STREQ(x,y) (strcmp(x,y) == 0) +#define STR vstring_str +#define LEN VSTRING_LEN +#define UPDATE(ptr,new) { myfree(ptr); ptr = mystrdup(new); } + + resolve_clnt_init(&reply); + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + + /* + * This may be a bit late in the game, but it is the most convenient + * place to scrutinize the destination address syntax. We have a + * complete queue file, so bouncing is easy. That luxury is not + * available to the cleanup service. The main issue is that we want + * to have this test in one place, instead of having to do this in + * every front-ent program. + */ + if ((at = strrchr(recipient->address, '@')) != 0 + && (at + 1)[strspn(at + 1, "[]0123456789.")] != 0 + && valid_hostname(at + 1, DONT_GRIPE) == 0) { + qmgr_bounce_recipient(message, recipient, + "bad host/domain syntax: \"%s\"", at + 1); + continue; + } + + /* + * Resolve the destination to (transport, nexthop, address). The + * result address may differ from the one specified by the sender. + */ + resolve_clnt_query(recipient->address, &reply); + if (message->filter_xport) { + vstring_strcpy(reply.transport, message->filter_xport); + if ((nexthop = split_at(STR(reply.transport), ':')) == 0 + || *nexthop == 0) + nexthop = var_myhostname; + vstring_strcpy(reply.nexthop, nexthop); + } else { + if (!STREQ(recipient->address, STR(reply.recipient))) + UPDATE(recipient->address, STR(reply.recipient)); + } + + /* + * XXX The nexthop destination is also used as lookup key for the + * per-destination queue. Fold the nexthop to lower case so that we + * don't have multiple queues for the same site. + */ + lowercase(STR(reply.nexthop)); + + /* + * Bounce recipients that have moved. We do it here instead of in the + * local delivery agent. The benefit is that we can bounce mail for + * virtual addresses, not just local addresses only, and that there + * is no need to run a local delivery agent just for the sake of + * relocation notices. The downside is that this table has no effect + * on local alias expansion results, so that mail will have to make + * almost an entire iteration through the mail system. + */ +#define IGNORE_ADDR_EXTENSION ((char **) 0) + + if (qmgr_relocated != 0) { + if ((newloc = mail_addr_find(qmgr_relocated, recipient->address, + IGNORE_ADDR_EXTENSION)) != 0) { + qmgr_bounce_recipient(message, recipient, + "user has moved to %s", newloc); + continue; + } else if (dict_errno != 0) { + qmgr_defer_recipient(message, recipient->address, + "relocated map lookup failure"); + continue; + } + } + + /* + * Bounce mail to non-existent users in virtual domains. + */ + if (qmgr_virtual != 0 + && (at = strrchr(recipient->address, '@')) != 0 + && !resolve_local(at + 1)) { + domain = lowercase(mystrdup(at + 1)); + junk = maps_find(qmgr_virtual, domain, 0); + myfree(domain); + if (junk) { + qmgr_bounce_recipient(message, recipient, + "unknown user: \"%s\"", recipient->address); + continue; + } + } + + /* + * Bounce recipient addresses that start with `-'. External commands + * may misinterpret such addresses as command-line options. + * + * In theory I could say people should always carefully set up their + * master.cf pipe mailer entries with `--' before the first + * non-option argument, but mistakes will happen regardless. + * + * Therefore the protection is put in place here, in the queue manager, + * where it cannot be bypassed. + */ + if (var_allow_min_user == 0 && recipient->address[0] == '-') { + qmgr_bounce_recipient(message, recipient, + "invalid recipient syntax: \"%s\"", + recipient->address); + continue; + } + + /* + * Queues are identified by the transport name and by the next-hop + * hostname. When the delivery agent accepts only one recipient per + * delivery, give each recipient its own queue, so that deliveries to + * different recipients of the same message can happen in parallel. + * This also has the benefit that one bad recipient cannot interfere + * with deliveries to other recipients. XXX Should split the address + * on the recipient delimiter if one is defined, but doing a proper + * job requires knowledge of local aliases. Yuck! I don't want to + * duplicate delivery-agent specific knowledge in the queue manager. + * XXX The nexthop field is overloaded to serve as destination and as + * queue name. Should have separate fields for queue name and for + * destination. + */ + at = strrchr(STR(reply.recipient), '@'); + len = (at ? (at - STR(reply.recipient)) : strlen(STR(reply.recipient))); + + /* + * Look up or instantiate the proper transport. We're working a + * little ahead, doing queue management stuff that used to be done + * way down. + */ + if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { + if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) + transport = qmgr_transport_create(STR(reply.transport)); + queue = 0; + } + if (transport->recipient_limit == 1) { + VSTRING_SPACE(reply.nexthop, len + 1); + memmove(STR(reply.nexthop) + len + 1, STR(reply.nexthop), + LEN(reply.nexthop) + 1); + memcpy(STR(reply.nexthop), STR(reply.recipient), len); + STR(reply.nexthop)[len] = '@'; + lowercase(STR(reply.nexthop)); + } + + /* + * Discard mail to the local double bounce address here, so this + * system can run without a local delivery agent. They'd still have + * to configure something for mail directed to the local postmaster, + * though, but that is an RFC requirement anyway. + */ + if (at == 0 || resolve_local(at + 1)) { + if (strncasecmp(STR(reply.recipient), var_double_bounce_sender, + len) == 0 + && !var_double_bounce_sender[len]) { + sent(message->queue_id, recipient->address, + "none", message->arrival_time, "discarded"); + deliver_completed(message->fp, recipient->offset); + msg_warn("%s: undeliverable postmaster notification discarded", + message->queue_id); + continue; + } + } + + /* + * Optionally defer deliveries over specific transports, unless the + * restriction is lifted temporarily. + */ + if (*var_defer_xports && (message->qflags & QMGR_FLUSH_DEAD) == 0) { + if (defer_xport_argv == 0) + defer_xport_argv = argv_split(var_defer_xports, " \t\r\n,"); + for (cpp = defer_xport_argv->argv; *cpp; cpp++) + if (strcmp(*cpp, STR(reply.transport)) == 0) + break; + if (*cpp) { + qmgr_defer_recipient(message, recipient->address, + "deferred transport"); + continue; + } + } + + /* + * XXX Gross hack alert. We want to group recipients by transport and + * by next-hop hostname, in order to minimize the number of network + * transactions. However, it would be wasteful to have an in-memory + * resolver reply structure for each in-core recipient. Instead, we + * bind each recipient to an in-core queue instance which is needed + * anyway. That gives all information needed for recipient grouping. + */ +#if 0 + + /* + * Look up or instantiate the proper transport. + */ + if (transport == 0 || !STREQ(transport->name, STR(reply.transport))) { + if ((transport = qmgr_transport_find(STR(reply.transport))) == 0) + transport = qmgr_transport_create(STR(reply.transport)); + queue = 0; + } +#endif + + /* + * This transport is dead. Defer delivery to this recipient. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) { + qmgr_defer_recipient(message, recipient->address, transport->reason); + continue; + } + + /* + * This transport is alive. Find or instantiate a queue for this + * recipient. + */ + if (queue == 0 || !STREQ(queue->name, STR(reply.nexthop))) { + if ((queue = qmgr_queue_find(transport, STR(reply.nexthop))) == 0) + queue = qmgr_queue_create(transport, STR(reply.nexthop)); + } + + /* + * This queue is dead. Defer delivery to this recipient. + */ + if (queue->window == 0) { + qmgr_defer_recipient(message, recipient->address, queue->reason); + continue; + } + + /* + * This queue is alive. Bind this recipient to this queue instance. + */ + recipient->queue = queue; + } + resolve_clnt_free(&reply); +} + +/* qmgr_message_assign - assign recipients to specific delivery requests */ + +static void qmgr_message_assign(QMGR_MESSAGE *message) +{ + QMGR_RCPT_LIST list = message->rcpt_list; + QMGR_RCPT *recipient; + QMGR_ENTRY *entry = 0; + QMGR_QUEUE *queue; + QMGR_JOB *job = 0; + QMGR_PEER *peer = 0; + + /* + * Try to bundle as many recipients in a delivery request as we can. When + * the recipient resolves to the same site and transport as the previous + * recipient, do not create a new queue entry, just move that recipient + * to the recipient list of the existing queue entry. All this provided + * that we do not exceed the transport-specific limit on the number of + * recipients per transaction. Skip recipients with a dead transport or + * destination. + */ +#define LIMIT_OK(limit, count) ((limit) == 0 || ((count) < (limit))) + + for (recipient = list.info; recipient < list.info + list.len; recipient++) { + if ((queue = recipient->queue) != 0) { + if (message->single_rcpt || entry == 0 || entry->queue != queue + || !LIMIT_OK(queue->transport->recipient_limit, + entry->rcpt_list.len)) { + + /* + * Lookup or instantiate the message job if necessary. + */ + if (job == 0 || queue->transport != job->transport) { + job = qmgr_job_obtain(message, queue->transport); + peer = 0; + } + + /* + * Lookup or instantiate job peer if necessary. + */ + if (peer == 0 || queue != peer->queue) { + if ((peer = qmgr_peer_find(job, queue)) == 0) + peer = qmgr_peer_create(job, queue); + } + + /* + * Create new peer entry. + */ + entry = qmgr_entry_create(peer, message); + job->read_entries++; + } + + /* + * Add the recipient to the current entry and increase all those + * recipient counters accordingly. + */ + qmgr_rcpt_list_add(&entry->rcpt_list, recipient->offset, recipient->address); + job->rcpt_count++; + message->rcpt_count++; + qmgr_recipient_count++; + } + } + + /* + * Release the message recipient list and reinitialize it for the next + * time. + */ + qmgr_rcpt_list_free(&message->rcpt_list); + qmgr_rcpt_list_init(&message->rcpt_list); + + /* + * Note that even if qmgr_job_obtain() reset the job candidate cache of + * all transports to which we assigned new recipients, this message may + * have other jobs which we didn't touch at all this time. But the number + * of unread recipients affecting the candidate selection might have + * changed considerably, so we must invalidate the caches if it might be + * of some use. + */ + for (job = message->job_list.next; job; job = job->message_peers.next) + if (job->selected_entries < job->read_entries + && job->blocker_tag != job->transport->blocker_tag) + job->transport->candidate_cache_current = 0; +} + +/* qmgr_message_move_limits - recycle unused recipient slots */ + +static void qmgr_message_move_limits(QMGR_MESSAGE *message) +{ + QMGR_JOB *job; + + for (job = message->job_list.next; job; job = job->message_peers.next) + qmgr_job_move_limits(job); +} + +/* qmgr_message_free - release memory for in-core message structure */ + +void qmgr_message_free(QMGR_MESSAGE *message) +{ + QMGR_JOB *job; + + if (message->refcount != 0) + msg_panic("qmgr_message_free: reference len: %d", message->refcount); + if (message->fp) + msg_panic("qmgr_message_free: queue file is open"); + while ((job = message->job_list.next) != 0) + qmgr_job_free(job); + myfree(message->queue_id); + myfree(message->queue_name); + if (message->sender) + myfree(message->sender); + if (message->verp_delims) + myfree(message->verp_delims); + if (message->errors_to) + myfree(message->errors_to); + if (message->return_receipt) + myfree(message->return_receipt); + if (message->filter_xport) + myfree(message->filter_xport); + if (message->inspect_xport) + myfree(message->inspect_xport); + qmgr_rcpt_list_free(&message->rcpt_list); + qmgr_message_count--; + myfree((char *) message); +} + +/* qmgr_message_alloc - create in-core message structure */ + +QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id, + int qflags) +{ + char *myname = "qmgr_message_alloc"; + QMGR_MESSAGE *message; + + if (msg_verbose) + msg_info("%s: %s %s", myname, queue_name, queue_id); + + /* + * Create an in-core message structure. + */ + message = qmgr_message_create(queue_name, queue_id, qflags); + + /* + * Extract message envelope information: time of arrival, sender address, + * recipient addresses. Skip files with malformed envelope information. + */ +#define QMGR_LOCK_MODE (MYFLOCK_OP_EXCLUSIVE | MYFLOCK_OP_NOWAIT) + + if (qmgr_message_open(message) < 0) { + qmgr_message_free(message); + return (0); + } + if (myflock(vstream_fileno(message->fp), INTERNAL_LOCK, QMGR_LOCK_MODE) < 0) { + msg_info("%s: skipped, still being delivered", queue_id); + qmgr_message_close(message); + qmgr_message_free(message); + return (QMGR_MESSAGE_LOCKED); + } + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + qmgr_message_free(message); + return (0); + } else { + + /* + * Reset the defer log. This code should not be here, but we must + * reset the defer log *after* acquiring the exclusive lock on the + * queue file and *before* resolving new recipients. Since all those + * operations are encapsulated so nicely by this routine, the defer + * log reset has to be done here as well. + */ + if (mail_queue_remove(MAIL_QUEUE_DEFER, queue_id) && errno != ENOENT) + msg_fatal("%s: %s: remove %s %s: %m", myname, + queue_id, MAIL_QUEUE_DEFER, queue_id); + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + if (message->rcpt_offset == 0) + qmgr_message_move_limits(message); + return (message); + } +} + +/* qmgr_message_realloc - refresh in-core message structure */ + +QMGR_MESSAGE *qmgr_message_realloc(QMGR_MESSAGE *message) +{ + char *myname = "qmgr_message_realloc"; + + /* + * Sanity checks. + */ + if (message->rcpt_offset <= 0) + msg_panic("%s: invalid offset: %ld", myname, message->rcpt_offset); + if (msg_verbose) + msg_info("%s: %s %s offset %ld", myname, message->queue_name, + message->queue_id, message->rcpt_offset); + + /* + * Extract recipient addresses. Skip files with malformed envelope + * information. + */ + if (qmgr_message_open(message) < 0) + return (0); + if (qmgr_message_read(message) < 0) { + qmgr_message_close(message); + return (0); + } else { + qmgr_message_sort(message); + qmgr_message_resolve(message); + qmgr_message_sort(message); + qmgr_message_assign(message); + qmgr_message_close(message); + if (message->rcpt_offset == 0) + qmgr_message_move_limits(message); + return (message); + } +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_move.c b/gnu/dist/postfix/src/nqmgr/qmgr_move.c new file mode 100644 index 00000000000..a4e55f0585f --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_move.c @@ -0,0 +1,104 @@ +/*++ +/* NAME +/* qmgr_move 3 +/* SUMMARY +/* move queue entries to another queue +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_move(from, to, time_stamp) +/* const char *from; +/* const char *to; +/* time_t time_stamp; +/* DESCRIPTION +/* The \fBqmgr_move\fR routine scans the \fIfrom\fR queue for entries +/* with valid queue names and moves them to the \fIto\fR queue. +/* If \fItime_stamp\fR is non-zero, the queue file time stamps are +/* set to the specified value. +/* Entries with invalid names are left alone. No attempt is made to +/* look for other badness such as multiple links or weird file types. +/* These issues are dealt with when a queue file is actually opened. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <string.h> +#include <utime.h> +#include <errno.h> + +/* Utility library. */ + +#include <msg.h> +#include <scan_dir.h> +#include <recipient_list.h> + +/* Global library. */ + +#include <mail_queue.h> +#include <mail_scan_dir.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_move - move queue entries to another queue, leave bad files alone */ + +void qmgr_move(const char *src_queue, const char *dst_queue, + time_t time_stamp) +{ + 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); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_peer.c b/gnu/dist/postfix/src/nqmgr/qmgr_peer.c new file mode 100644 index 00000000000..9b61e64e521 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_peer.c @@ -0,0 +1,136 @@ +/*++ +/* NAME +/* qmgr_peer 3 +/* SUMMARY +/* per-job peers +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_PEER *qmgr_peer_create(job, queue) +/* QMGR_JOB *job; +/* QMGR_QUEUE *queue; +/* +/* QMGR_PEER *qmgr_peer_find(job, queue) +/* QMGR_JOB *job; +/* QMGR_QUEUE *queue; +/* +/* void qmgr_peer_free(peer) +/* QMGR_PEER *peer; +/* +/* QMGR_PEER *qmgr_peer_select(job) +/* QMGR_JOB *job; +/* +/* DESCRIPTION +/* These routines add/delete/manipulate per-job peers. +/* Each queue corresponds to a specific job and destination. +/* It is similar to per-transport queue structure, but groups +/* only the entries of the given job. +/* +/* qmgr_peer_create() creates an empty peer structure for the named +/* job and destination. It is an error to call this function +/* if a peer for given combination already exists. +/* +/* qmgr_peer_find() looks up the peer for the named destination +/* for the named job. A null result means that the peer +/* was not found. +/* +/* qmgr_peer_free() disposes of a per-job peer after all +/* its entries have been taken care of. It is an error to dispose +/* of a peer still in use. +/* +/* qmgr_peer_select() attempts to find a peer of named job that +/* has messages pending delivery. This routine implements +/* round-robin search among job's peers. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Patrik Rak +/* patrik@raxoft.cz +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <mymalloc.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_peer_create - create and initialize message peer structure */ + +QMGR_PEER *qmgr_peer_create(QMGR_JOB *job, QMGR_QUEUE *queue) +{ + QMGR_PEER *peer; + + peer = (QMGR_PEER *) mymalloc(sizeof(QMGR_PEER)); + peer->queue = queue; + peer->job = job; + QMGR_LIST_APPEND(job->peer_list, peer, peers); + htable_enter(job->peer_byname, queue->name, (char *) peer); + peer->refcount = 0; + QMGR_LIST_INIT(peer->entry_list); + return (peer); +} + +/* qmgr_peer_free - release peer structure */ + +void qmgr_peer_free(QMGR_PEER *peer) +{ + char *myname = "qmgr_peer_free"; + QMGR_JOB *job = peer->job; + QMGR_QUEUE *queue = peer->queue; + + /* + * Sanity checks. It is an error to delete a referenced peer structure. + */ + if (peer->refcount != 0) + msg_panic("%s: refcount: %d", myname, peer->refcount); + if (peer->entry_list.next != 0) + msg_panic("%s: entry list not empty: %s", myname, queue->name); + + QMGR_LIST_UNLINK(job->peer_list, QMGR_PEER *, peer, peers); + htable_delete(job->peer_byname, queue->name, (void (*) (char *)) 0); + myfree((char *) peer); +} + +/* qmgr_peer_find - lookup peer associated with given job and queue */ + +QMGR_PEER *qmgr_peer_find(QMGR_JOB *job, QMGR_QUEUE *queue) +{ + return ((QMGR_PEER *) htable_find(job->peer_byname, queue->name)); +} + +/* qmgr_peer_select - select next peer suitable for delivery within given job */ + +QMGR_PEER *qmgr_peer_select(QMGR_JOB *job) +{ + QMGR_PEER *peer; + QMGR_QUEUE *queue; + + /* + * If we find a suitable site, rotate the list to enforce round-robin + * selection. See similar selection code in qmgr_transport_select(). + */ + for (peer = job->peer_list.next; peer; peer = peer->peers.next) { + queue = peer->queue; + if (queue->window > queue->busy_refcount && peer->entry_list.next != 0) { + QMGR_LIST_ROTATE(job->peer_list, peer, peers); + if (msg_verbose) + msg_info("qmgr_peer_select: %s %s %s (%d of %d)", + job->message->queue_id, queue->transport->name, queue->name, + queue->busy_refcount + 1, queue->window); + return (peer); + } + } + return (0); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_queue.c b/gnu/dist/postfix/src/nqmgr/qmgr_queue.c new file mode 100644 index 00000000000..d4aced58e23 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_queue.c @@ -0,0 +1,251 @@ +/*++ +/* NAME +/* qmgr_queue 3 +/* SUMMARY +/* per-destination queues +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* int qmgr_queue_count; +/* +/* QMGR_QUEUE *qmgr_queue_create(transport, site) +/* QMGR_TRANSPORT *transport; +/* const char *site; +/* +/* void qmgr_queue_done(queue) +/* QMGR_QUEUE *queue; +/* +/* QMGR_QUEUE *qmgr_queue_find(transport, site) +/* QMGR_TRANSPORT *transport; +/* const char *site; +/* +/* void qmgr_queue_throttle(queue, reason) +/* QMGR_QUEUE *queue; +/* const char *reason; +/* +/* void qmgr_queue_unthrottle(queue) +/* QMGR_QUEUE *queue; +/* DESCRIPTION +/* These routines add/delete/manipulate per-destination queues. +/* Each queue corresponds to a specific transport and destination. +/* Each queue has a `todo' list of delivery requests for that +/* destination, and a `busy' list of delivery requests in progress. +/* +/* qmgr_queue_count is a global counter for the total number +/* of in-core queue structures. +/* +/* qmgr_queue_create() creates an empty queue for the named +/* transport and destination. The queue is given an initial +/* concurrency limit as specified with the +/* \fIinitial_destination_concurrency\fR configuration parameter, +/* provided that it does not exceed the transport-specific +/* concurrency limit. +/* +/* qmgr_queue_done() disposes of a per-destination queue after all +/* its entries have been taken care of. It is an error to dispose +/* of a dead queue. +/* +/* qmgr_queue_find() looks up the queue for the named destination +/* for the named transport. A null result means that the queue +/* was not found. +/* +/* qmgr_queue_throttle() handles a delivery error, and decrements the +/* concurrency limit for the destination. When the concurrency limit +/* for a destination becomes zero, qmgr_queue_throttle() starts a timer +/* to re-enable delivery to the destination after a configurable delay. +/* +/* qmgr_queue_unthrottle() undoes qmgr_queue_throttle()'s effects. +/* The concurrency limit for the destination is incremented, +/* provided that it does not exceed the destination concurrency +/* limit specified for the transport. This routine implements +/* "slow open" mode, and eliminates the "thundering herd" problem. +/* DIAGNOSTICS +/* Panic: consistency check failure. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <time.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <events.h> +#include <htable.h> + +/* Global library. */ + +#include <mail_params.h> +#include <recipient_list.h> + +/* Application-specific. */ + +#include "qmgr.h" + +int qmgr_queue_count; + +/* qmgr_queue_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_queue_unthrottle_wrapper(int unused_event, char *context) +{ + QMGR_QUEUE *queue = (QMGR_QUEUE *) context; + + /* + * This routine runs when a wakeup timer goes off; it does not run in the + * context of some queue manipulation. Therefore, it is safe to discard + * this in-core queue when it is empty and when this site is not dead. + */ + qmgr_queue_unthrottle(queue); + if (queue->window > 0 && queue->todo.next == 0 && queue->busy.next == 0) + qmgr_queue_done(queue); +} + +/* qmgr_queue_unthrottle - give this destination another chance */ + +void qmgr_queue_unthrottle(QMGR_QUEUE *queue) +{ + char *myname = "qmgr_queue_unthrottle"; + QMGR_TRANSPORT *transport = queue->transport; + + if (msg_verbose) + msg_info("%s: queue %s", myname, queue->name); + + /* + * Special case when this site was dead. + */ + if (queue->window == 0) { + event_cancel_timer(qmgr_queue_unthrottle_wrapper, (char *) queue); + if (queue->reason == 0) + msg_panic("%s: queue %s: window 0 reason 0", myname, queue->name); + myfree(queue->reason); + queue->reason = 0; + queue->window = transport->init_dest_concurrency; + return; + } + + /* + * Increase the destination's concurrency limit until we reach the + * transport's concurrency limit. Allow for a margin the size of the + * initial destination concurrency, so that we're not too gentle. + */ + if (transport->dest_concurrency_limit == 0 + || transport->dest_concurrency_limit > queue->window) + if (queue->window < queue->busy_refcount + transport->init_dest_concurrency) + queue->window++; +} + +/* qmgr_queue_throttle - handle destination delivery failure */ + +void qmgr_queue_throttle(QMGR_QUEUE *queue, const char *reason) +{ + char *myname = "qmgr_queue_throttle"; + + /* + * Sanity checks. + */ + if (queue->reason) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->reason); + if (msg_verbose) + msg_info("%s: queue %s: %s", myname, queue->name, reason); + + /* + * Decrease the destination's concurrency limit until we reach zero, at + * which point the destination is declared dead. Decrease the concurrency + * limit by one, instead of using actual concurrency - 1, to avoid + * declaring a host dead after just one single delivery failure. + */ + if (queue->window > 0) + queue->window--; + + /* + * Special case for a site that just was declared dead. + */ + if (queue->window == 0) { + queue->reason = mystrdup(reason); + event_request_timer(qmgr_queue_unthrottle_wrapper, + (char *) queue, var_min_backoff_time); + } +} + +/* qmgr_queue_done - delete in-core queue for site */ + +void qmgr_queue_done(QMGR_QUEUE *queue) +{ + char *myname = "qmgr_queue_done"; + QMGR_TRANSPORT *transport = queue->transport; + + /* + * Sanity checks. It is an error to delete an in-core queue with pending + * messages or timers. + */ + if (queue->busy_refcount != 0 || queue->todo_refcount != 0) + msg_panic("%s: refcount: %d", myname, + queue->busy_refcount + queue->todo_refcount); + if (queue->todo.next || queue->busy.next) + msg_panic("%s: queue not empty: %s", myname, queue->name); + if (queue->window <= 0) + msg_panic("%s: window %d", myname, queue->window); + if (queue->reason) + msg_panic("%s: queue %s: spurious reason %s", + myname, queue->name, queue->reason); + + /* + * Clean up this in-core queue. + */ + QMGR_LIST_UNLINK(transport->queue_list, QMGR_QUEUE *, queue, peers); + htable_delete(transport->queue_byname, queue->name, (void (*) (char *)) 0); + myfree(queue->name); + qmgr_queue_count--; + myfree((char *) queue); +} + +/* qmgr_queue_create - create in-core queue for site */ + +QMGR_QUEUE *qmgr_queue_create(QMGR_TRANSPORT *transport, const char *site) +{ + QMGR_QUEUE *queue; + + /* + * If possible, choose an initial concurrency of > 1 so that one bad + * message or one bad network won't slow us down unnecessarily. + */ + + queue = (QMGR_QUEUE *) mymalloc(sizeof(QMGR_QUEUE)); + qmgr_queue_count++; + queue->name = mystrdup(site); + queue->todo_refcount = 0; + queue->busy_refcount = 0; + queue->transport = transport; + queue->window = transport->init_dest_concurrency; + QMGR_LIST_INIT(queue->todo); + QMGR_LIST_INIT(queue->busy); + queue->reason = 0; + queue->blocker_tag = 0; + QMGR_LIST_APPEND(transport->queue_list, queue, peers); + htable_enter(transport->queue_byname, site, (char *) queue); + return (queue); +} + +/* qmgr_queue_find - find in-core queue for site */ + +QMGR_QUEUE *qmgr_queue_find(QMGR_TRANSPORT *transport, const char *site) +{ + return ((QMGR_QUEUE *) htable_find(transport->queue_byname, site)); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_rcpt_list.c b/gnu/dist/postfix/src/nqmgr/qmgr_rcpt_list.c new file mode 100644 index 00000000000..2bb292e016e --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_rcpt_list.c @@ -0,0 +1,96 @@ +/*++ +/* NAME +/* qmgr_rcpt_list 3 +/* SUMMARY +/* in-core recipient structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* void qmgr_rcpt_list_init(list) +/* QMGR_RCPT_LIST *list; +/* +/* void qmgr_rcpt_list_add(list, offset, recipient) +/* QMGR_RCPT_LIST *list; +/* long offset; +/* const char *recipient; +/* +/* void qmgr_rcpt_list_free(list) +/* QMGR_RCPT_LIST *list; +/* DESCRIPTION +/* This module maintains lists of queue manager recipient structures. +/* These structures are extended versions of the structures maintained +/* by the recipient_list(3) module. The extension is that the queue +/* manager version of a recipient can have a reference to a queue +/* structure. +/* +/* qmgr_rcpt_list_init() creates an empty recipient structure list. +/* The list argument is initialized such that it can be given to +/* qmgr_rcpt_list_add() and qmgr_rcpt_list_free(). +/* +/* qmgr_rcpt_list_add() adds a recipient to the specified list. +/* The recipient name is copied. +/* +/* qmgr_rcpt_list_free() releases memory for the specified list +/* of recipient structures. +/* SEE ALSO +/* qmgr_rcpt_list(3h) data structure +/* recipient_list(3) same code, different data structure. +/* DIAGNOSTICS +/* Fatal errors: memory allocation. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <mymalloc.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_rcpt_list_init - initialize */ + +void qmgr_rcpt_list_init(QMGR_RCPT_LIST *list) +{ + list->avail = 1; + list->len = 0; + list->info = (QMGR_RCPT *) mymalloc(sizeof(QMGR_RCPT)); +} + +/* qmgr_rcpt_list_add - add rcpt to list */ + +void qmgr_rcpt_list_add(QMGR_RCPT_LIST *list, long offset, const char *rcpt) +{ + if (list->len >= list->avail) { + list->avail *= 2; + list->info = (QMGR_RCPT *) + myrealloc((char *) list->info, list->avail * sizeof(QMGR_RCPT)); + } + list->info[list->len].address = mystrdup(rcpt); + list->info[list->len].offset = offset; + list->info[list->len].queue = 0; + list->len++; +} + +/* qmgr_rcpt_list_free - release memory for in-core recipient structure */ + +void qmgr_rcpt_list_free(QMGR_RCPT_LIST *list) +{ + QMGR_RCPT *rcpt; + + for (rcpt = list->info; rcpt < list->info + list->len; rcpt++) + myfree(rcpt->address); + myfree((char *) list->info); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_scan.c b/gnu/dist/postfix/src/nqmgr/qmgr_scan.c new file mode 100644 index 00000000000..e0d9fc079ab --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_scan.c @@ -0,0 +1,159 @@ +/*++ +/* NAME +/* qmgr_scan 3 +/* SUMMARY +/* queue scanning +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_SCAN *qmgr_scan_create(queue_name) +/* const char *queue_name; +/* +/* char *qmgr_scan_next(scan_info) +/* QMGR_SCAN *scan_info; +/* +/* void qmgr_scan_request(scan_info, flags) +/* QMGR_SCAN *scan_info; +/* int flags; +/* DESCRIPTION +/* This module implements queue scans. A queue scan always runs +/* to completion, so that all files get a fair chance. The caller +/* can request that a queue scan be restarted once it completes. +/* +/* qmgr_scan_create() creates a context for scanning the named queue, +/* but does not start a queue scan. +/* +/* qmgr_scan_next() returns the base name of the next queue file. +/* A null pointer means that no file was found. qmgr_scan_next() +/* automagically restarts a queue scan when a scan request had +/* arrived while the scan was in progress. +/* +/* qmgr_scan_request() records a request for the next queue scan. The +/* flags argument is the bit-wise OR of zero or more of the following, +/* unrecognized flags being ignored: +/* .IP QMGR_FLUSH_DEAD +/* Forget state information about dead hosts or transports. This +/* request takes effect upon the next queue scan. +/* .IP QMGR_SCAN_ALL +/* Ignore queue file time stamps. +/* This flag is passed on to the qmgr_active_feed() routine. +/* .IP QMGR_SCAN_START +/* Start a queue scan when none is in progress, or restart the +/* current scan upon completion. +/* DIAGNOSTICS +/* Fatal: out of memory. +/* Panic: interface violations, internal consistency errors. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <scan_dir.h> + +/* Global library. */ + +#include <mail_scan_dir.h> + +/* Application-specific. */ + +#include "qmgr.h" + +/* qmgr_scan_start - start queue scan */ + +static void qmgr_scan_start(QMGR_SCAN *scan_info) +{ + char *myname = "qmgr_scan_start"; + + /* + * Sanity check. + */ + if (scan_info->handle) + msg_panic("%s: %s queue scan in progress", + myname, scan_info->queue); + + /* + * Give the poor tester a clue. + */ + if (msg_verbose) + msg_info("%s: %sstart %s queue scan", + myname, + scan_info->nflags & QMGR_SCAN_START ? "re" : "", + scan_info->queue); + + /* + * Optionally forget all dead host information. + */ + if (scan_info->nflags & QMGR_FLUSH_DEAD) + qmgr_enable_all(); + + /* + * Start or restart the scan. + */ + scan_info->flags = scan_info->nflags; + scan_info->nflags = 0; + scan_info->handle = scan_dir_open(scan_info->queue); +} + +/* qmgr_scan_request - request for future scan */ + +void qmgr_scan_request(QMGR_SCAN *scan_info, int flags) +{ + + /* + * If a scan is in progress, just record the request. + */ + scan_info->nflags |= flags; + if (scan_info->handle == 0 && (flags & QMGR_SCAN_START) != 0) { + scan_info->nflags &= ~QMGR_SCAN_START; + qmgr_scan_start(scan_info); + } +} + +/* qmgr_scan_next - look for next queue file */ + +char *qmgr_scan_next(QMGR_SCAN *scan_info) +{ + char *path = 0; + + /* + * Restart the scan if we reach the end and a queue scan request has + * arrived in the mean time. + */ + if (scan_info->handle && (path = mail_scan_dir_next(scan_info->handle)) == 0) { + scan_info->handle = scan_dir_close(scan_info->handle); + if (msg_verbose && (scan_info->nflags & QMGR_SCAN_START) == 0) + msg_info("done %s queue scan", scan_info->queue); + } + if (!scan_info->handle && (scan_info->nflags & QMGR_SCAN_START)) { + qmgr_scan_start(scan_info); + path = mail_scan_dir_next(scan_info->handle); + } + return (path); +} + +/* qmgr_scan_create - create queue scan context */ + +QMGR_SCAN *qmgr_scan_create(const char *queue) +{ + QMGR_SCAN *scan_info; + + scan_info = (QMGR_SCAN *) mymalloc(sizeof(*scan_info)); + scan_info->queue = mystrdup(queue); + scan_info->flags = scan_info->nflags = 0; + scan_info->handle = 0; + return (scan_info); +} diff --git a/gnu/dist/postfix/src/nqmgr/qmgr_transport.c b/gnu/dist/postfix/src/nqmgr/qmgr_transport.c new file mode 100644 index 00000000000..204880962b3 --- /dev/null +++ b/gnu/dist/postfix/src/nqmgr/qmgr_transport.c @@ -0,0 +1,381 @@ +/*++ +/* NAME +/* qmgr_transport 3 +/* SUMMARY +/* per-transport data structures +/* SYNOPSIS +/* #include "qmgr.h" +/* +/* QMGR_TRANSPORT *qmgr_transport_create(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_find(name) +/* const char *name; +/* +/* QMGR_TRANSPORT *qmgr_transport_select() +/* +/* void qmgr_transport_alloc(transport, notify) +/* QMGR_TRANSPORT *transport; +/* void (*notify)(QMGR_TRANSPORT *transport, VSTREAM *fp); +/* +/* void qmgr_transport_throttle(transport, reason) +/* QMGR_TRANSPORT *transport; +/* const char *reason; +/* +/* void qmgr_transport_unthrottle(transport) +/* QMGR_TRANSPORT *transport; +/* DESCRIPTION +/* This module organizes the world by message transport type. +/* Each transport can have zero or more destination queues +/* associated with it. +/* +/* qmgr_transport_create() instantiates a data structure for the +/* named transport type. +/* +/* qmgr_transport_find() looks up an existing message transport +/* data structure. +/* +/* qmgr_transport_select() attempts to find a transport that +/* has messages pending delivery. This routine implements +/* round-robin search among transports. +/* +/* qmgr_transport_alloc() allocates a delivery process for the +/* specified transport type. Allocation is performed asynchronously. +/* When a process becomes available, the application callback routine +/* is invoked with as arguments the transport and a stream that +/* is connected to a delivery process. It is an error to call +/* qmgr_transport_alloc() while delivery process allocation for +/* the same transport is in progress. +/* +/* qmgr_transport_throttle blocks further allocation of delivery +/* processes for the named transport. Attempts to throttle a +/* throttled transport are ignored. +/* +/* qmgr_transport_unthrottle() undoes qmgr_transport_throttle(). +/* Attempts to unthrottle a non-throttled transport are ignored. +/* DIAGNOSTICS +/* Panic: consistency check failure. Fatal: out of memory. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Scheduler enhancements: +/* Patrik Rak +/* Modra 6 +/* 155 00, Prague, Czech Republic +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <unistd.h> + +/* Utility library. */ + +#include <msg.h> +#include <htable.h> +#include <events.h> +#include <mymalloc.h> +#include <vstream.h> +#include <iostuff.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <recipient_list.h> +#include <mail_conf.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include "qmgr.h" + +HTABLE *qmgr_transport_byname; /* transport by name */ +QMGR_TRANSPORT_LIST qmgr_transport_list;/* transports, round robin */ + + /* + * A local structure to remember a delivery process allocation request. + */ +typedef struct QMGR_TRANSPORT_ALLOC QMGR_TRANSPORT_ALLOC; + +struct QMGR_TRANSPORT_ALLOC { + QMGR_TRANSPORT *transport; /* transport context */ + VSTREAM *stream; /* delivery service stream */ + QMGR_TRANSPORT_ALLOC_NOTIFY notify; /* application call-back routine */ +}; + +/* qmgr_transport_unthrottle_wrapper - in case (char *) != (struct *) */ + +static void qmgr_transport_unthrottle_wrapper(int unused_event, char *context) +{ + qmgr_transport_unthrottle((QMGR_TRANSPORT *) context); +} + +/* qmgr_transport_unthrottle - open the throttle */ + +void qmgr_transport_unthrottle(QMGR_TRANSPORT *transport) +{ + char *myname = "qmgr_transport_unthrottle"; + + /* + * This routine runs after expiration of the timer set by + * qmgr_transport_throttle(), or whenever a delivery transport has been + * used without malfunction. In either case, we enable delivery again if + * the transport was blocked, otherwise the request is ignored. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) != 0) { + if (msg_verbose) + msg_info("%s: transport %s", myname, transport->name); + transport->flags &= ~QMGR_TRANSPORT_STAT_DEAD; + if (transport->reason == 0) + msg_panic("%s: transport %s: null reason", myname, transport->name); + myfree(transport->reason); + transport->reason = 0; + event_cancel_timer(qmgr_transport_unthrottle_wrapper, + (char *) transport); + } +} + +/* qmgr_transport_throttle - disable delivery process allocation */ + +void qmgr_transport_throttle(QMGR_TRANSPORT *transport, const char *reason) +{ + char *myname = "qmgr_transport_throttle"; + + /* + * We are unable to connect to a deliver process for this type of message + * transport. Instead of hosing the system by retrying in a tight loop, + * back off and disable this transport type for a while. + */ + if ((transport->flags & QMGR_TRANSPORT_STAT_DEAD) == 0) { + if (msg_verbose) + msg_info("%s: transport %s: reason: %s", + myname, transport->name, reason); + transport->flags |= QMGR_TRANSPORT_STAT_DEAD; + if (transport->reason) + msg_panic("%s: transport %s: spurious reason: %s", + myname, transport->name, transport->reason); + transport->reason = mystrdup(reason); + event_request_timer(qmgr_transport_unthrottle_wrapper, + (char *) transport, var_transport_retry_time); + } +} + +/* qmgr_transport_abort - transport connect watchdog */ + +static void qmgr_transport_abort(int unused_event, char *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + msg_fatal("timeout connecting to transport: %s", alloc->transport->name); +} + +/* qmgr_transport_event - delivery process availability notice */ + +static void qmgr_transport_event(int unused_event, char *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + /* + * This routine notifies the application when the request given to + * qmgr_transport_alloc() completes. + */ + if (msg_verbose) + msg_info("transport_event: %s", alloc->transport->name); + + /* + * Connection request completed. Stop the watchdog timer. + */ + event_cancel_timer(qmgr_transport_abort, context); + + /* + * Disable further read events that end up calling this function. + */ + event_disable_readwrite(vstream_fileno(alloc->stream)); + alloc->transport->flags &= ~QMGR_TRANSPORT_STAT_BUSY; + + /* + * Notify the requestor. + */ + alloc->notify(alloc->transport, alloc->stream); + myfree((char *) alloc); +} + +#ifdef UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT + +/* qmgr_transport_connect - handle connection request completion */ + +static void qmgr_transport_connect(int unused_event, char *context) +{ + QMGR_TRANSPORT_ALLOC *alloc = (QMGR_TRANSPORT_ALLOC *) context; + + /* + * This code is necessary for some versions of LINUX, where connect(2) + * blocks until the application performs an accept(2). Reportedly, the + * same can happen on Solaris 2.5.1. + */ + event_disable_readwrite(vstream_fileno(alloc->stream)); + non_blocking(vstream_fileno(alloc->stream), BLOCKING); + event_enable_read(vstream_fileno(alloc->stream), + qmgr_transport_event, (char *) alloc); +} + +#endif + +/* qmgr_transport_select - select transport for allocation */ + +QMGR_TRANSPORT *qmgr_transport_select(void) +{ + QMGR_TRANSPORT *xport; + QMGR_QUEUE *queue; + + /* + * If we find a suitable transport, rotate the list of transports to + * effectuate round-robin selection. See similar selection code in + * qmgr_peer_select(). + */ +#define STAY_AWAY (QMGR_TRANSPORT_STAT_BUSY | QMGR_TRANSPORT_STAT_DEAD) + + for (xport = qmgr_transport_list.next; xport; xport = xport->peers.next) { + if (xport->flags & STAY_AWAY) + continue; + for (queue = xport->queue_list.next; queue; queue = queue->peers.next) { + if (queue->window > queue->busy_refcount && queue->todo.next != 0) { + QMGR_LIST_ROTATE(qmgr_transport_list, xport, peers); + if (msg_verbose) + msg_info("qmgr_transport_select: %s", xport->name); + return (xport); + } + } + } + return (0); +} + +/* qmgr_transport_alloc - allocate delivery process */ + +void qmgr_transport_alloc(QMGR_TRANSPORT *transport, QMGR_TRANSPORT_ALLOC_NOTIFY notify) +{ + QMGR_TRANSPORT_ALLOC *alloc; + VSTREAM *stream; + + /* + * Sanity checks. + */ + if (transport->flags & QMGR_TRANSPORT_STAT_DEAD) + msg_panic("qmgr_transport: dead transport: %s", transport->name); + if (transport->flags & QMGR_TRANSPORT_STAT_BUSY) + msg_panic("qmgr_transport: nested allocation: %s", transport->name); + + /* + * Connect to the well-known port for this delivery service, and wake up + * when a process announces its availability. In the mean time, block out + * other delivery process allocation attempts for this transport. In case + * of problems, back off. Do not hose the system when it is in trouble + * already. + */ +#ifdef UNIX_DOMAIN_CONNECT_BLOCKS_FOR_ACCEPT +#define BLOCK_MODE NON_BLOCKING +#define ENABLE_EVENTS event_enable_write +#define EVENT_HANDLER qmgr_transport_connect +#else +#define BLOCK_MODE BLOCKING +#define ENABLE_EVENTS event_enable_read +#define EVENT_HANDLER qmgr_transport_event +#endif + + if ((stream = mail_connect(MAIL_CLASS_PRIVATE, transport->name, BLOCK_MODE)) == 0) { + msg_warn("connect to transport %s: %m", transport->name); + qmgr_transport_throttle(transport, "transport is unavailable"); + return; + } + alloc = (QMGR_TRANSPORT_ALLOC *) mymalloc(sizeof(*alloc)); + alloc->stream = stream; + alloc->transport = transport; + alloc->notify = notify; + transport->flags |= QMGR_TRANSPORT_STAT_BUSY; + ENABLE_EVENTS(vstream_fileno(alloc->stream), EVENT_HANDLER, (char *) alloc); + + /* + * Guard against broken systems. + */ + event_request_timer(qmgr_transport_abort, (char *) alloc, + var_daemon_timeout); +} + +/* qmgr_transport_create - create transport instance */ + +QMGR_TRANSPORT *qmgr_transport_create(const char *name) +{ + QMGR_TRANSPORT *transport; + + if (htable_find(qmgr_transport_byname, name) != 0) + msg_panic("qmgr_transport_create: transport exists: %s", name); + transport = (QMGR_TRANSPORT *) mymalloc(sizeof(QMGR_TRANSPORT)); + transport->flags = 0; + transport->name = mystrdup(name); + + /* + * Use global configuration settings or transport-specific settings. + */ + transport->dest_concurrency_limit = + get_mail_conf_int2(name, _DEST_CON_LIMIT, + var_dest_con_limit, 0, 0); + transport->recipient_limit = + get_mail_conf_int2(name, _DEST_RCPT_LIMIT, + var_dest_rcpt_limit, 0, 0); + + if (transport->dest_concurrency_limit == 0 + || transport->dest_concurrency_limit >= var_init_dest_concurrency) + transport->init_dest_concurrency = var_init_dest_concurrency; + else + transport->init_dest_concurrency = transport->dest_concurrency_limit; + + transport->slot_cost = get_mail_conf_int2(name, _DELIVERY_SLOT_COST, + var_delivery_slot_cost, 0, 0); + transport->slot_loan = get_mail_conf_int2(name, _DELIVERY_SLOT_LOAN, + var_delivery_slot_loan, 0, 0); + transport->slot_loan_factor = + 100 - get_mail_conf_int2(name, _DELIVERY_SLOT_DISCOUNT, + var_delivery_slot_discount, 0, 100); + transport->min_slots = get_mail_conf_int2(name, _MIN_DELIVERY_SLOTS, + var_min_delivery_slots, 0, 0); + transport->rcpt_unused = get_mail_conf_int2(name, _XPORT_RCPT_LIMIT, + var_xport_rcpt_limit, 0, 0); + transport->rcpt_per_stack = get_mail_conf_int2(name, _STACK_RCPT_LIMIT, + var_stack_rcpt_limit, 0, 0); + + transport->queue_byname = htable_create(0); + QMGR_LIST_INIT(transport->queue_list); + transport->job_byname = htable_create(0); + QMGR_LIST_INIT(transport->job_list); + QMGR_LIST_INIT(transport->job_bytime); + transport->job_current = 0; + transport->job_next_unread = 0; + transport->candidate_cache = 0; + transport->candidate_cache_current = 0; + transport->candidate_cache_time = (time_t) 0; + transport->blocker_tag = 1; + transport->reason = 0; + if (qmgr_transport_byname == 0) + qmgr_transport_byname = htable_create(10); + htable_enter(qmgr_transport_byname, name, (char *) transport); + QMGR_LIST_PREPEND(qmgr_transport_list, transport, peers); + if (msg_verbose) + msg_info("qmgr_transport_create: %s concurrency %d recipients %d", + transport->name, transport->dest_concurrency_limit, + transport->recipient_limit); + return (transport); +} + +/* qmgr_transport_find - find transport instance */ + +QMGR_TRANSPORT *qmgr_transport_find(const char *name) +{ + return ((QMGR_TRANSPORT *) htable_find(qmgr_transport_byname, name)); +} diff --git a/gnu/dist/postfix/src/pickup/Makefile.in b/gnu/dist/postfix/src/pickup/Makefile.in index 19f5688fc74..a9006d5edd3 100644 --- a/gnu/dist/postfix/src/pickup/Makefile.in +++ b/gnu/dist/postfix/src/pickup/Makefile.in @@ -60,13 +60,14 @@ pickup.o: ../../include/scan_dir.h pickup.o: ../../include/vstring.h pickup.o: ../../include/vbuf.h pickup.o: ../../include/vstream.h -pickup.o: ../../include/open_as.h -pickup.o: ../../include/set_eugid.h +pickup.o: ../../include/set_ugid.h +pickup.o: ../../include/safe_open.h pickup.o: ../../include/mail_queue.h pickup.o: ../../include/mail_open_ok.h pickup.o: ../../include/mymalloc.h pickup.o: ../../include/mail_proto.h pickup.o: ../../include/iostuff.h +pickup.o: ../../include/attr.h pickup.o: ../../include/cleanup_user.h pickup.o: ../../include/mail_date.h pickup.o: ../../include/mail_params.h diff --git a/gnu/dist/postfix/src/pickup/pickup.c b/gnu/dist/postfix/src/pickup/pickup.c index 38d798e39cc..994a6328f58 100644 --- a/gnu/dist/postfix/src/pickup/pickup.c +++ b/gnu/dist/postfix/src/pickup/pickup.c @@ -7,8 +7,8 @@ /* \fBpickup\fR [generic Postfix daemon options] /* DESCRIPTION /* The \fBpickup\fR daemon waits for hints that new mail has been -/* dropped into the world-writable \fBmaildrop\fR directory, and -/* feeds it into the \fBcleanup\fR(8) daemon. +/* dropped into the \fBmaildrop\fR directory, and feeds it into the +/* \fBcleanup\fR(8) daemon. /* Ill-formatted files are deleted without notifying the originator. /* This program expects to be run from the \fBmaster\fR(8) process /* manager. @@ -58,6 +58,7 @@ /* SEE ALSO /* cleanup(8) message canonicalization /* master(8) process manager +/* sendmail(1), postdrop(8) mail posting agent /* syslogd(8) system logging /* LICENSE /* .ad @@ -89,8 +90,8 @@ #include <scan_dir.h> #include <vstring.h> #include <vstream.h> -#include <open_as.h> -#include <set_eugid.h> +#include <set_ugid.h> +#include <safe_open.h> /* Global library. */ @@ -295,7 +296,9 @@ static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup, * bounce, the cleanup service can report only soft errors here. */ rec_fputs(cleanup, REC_TYPE_END, ""); - if (mail_scan(cleanup, "%d", &status) != 1) + if (attr_scan(cleanup, ATTR_FLAG_MISSING, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &status, + ATTR_TYPE_END) != 1) return (cleanup_service_error(info, CLEANUP_STAT_WRITE)); /* @@ -315,8 +318,7 @@ static int pickup_copy(VSTREAM *qfile, VSTREAM *cleanup, static int pickup_file(PICKUP_INFO *info) { - struct stat st; - VSTRING *buf; + VSTRING *buf = vstring_alloc(100); int status; VSTREAM *qfile; VSTREAM *cleanup; @@ -329,29 +331,16 @@ static int pickup_file(PICKUP_INFO *info) * Perhaps we should save "bad" files elsewhere for further inspection. * XXX How can we delete a file when open() fails with ENOENT? */ - fd = open_as(info->path, O_RDONLY | O_NONBLOCK, 0, - info->st.st_uid, info->st.st_gid); - if (fd < 0) { + qfile = safe_open(info->path, O_RDONLY | O_NONBLOCK, 0, + (struct stat *) 0, -1, -1, buf); + if (qfile == 0) { if (errno != ENOENT) - msg_fatal("open input file %s: %m", info->path); - msg_warn("open input file %s: %m", info->path); - return (REMOVE_MESSAGE_FILE); + msg_warn("open input file %s: %s", info->path, vstring_str(buf)); + vstring_free(buf); + return (errno == EACCES ? KEEP_MESSAGE_FILE : REMOVE_MESSAGE_FILE); } /* - * Like safe_open(pat, O_RDONLY, 0), but without any link count checks. - */ - if (fstat(fd, &st) < 0) - msg_fatal("fstat: %m"); - if (st.st_dev != info->st.st_dev - || st.st_ino != info->st.st_ino - || st.st_mode != info->st.st_mode) { - msg_warn("%s: uid %ld: file has changed", info->path, (long) st.st_uid); - return (REMOVE_MESSAGE_FILE); - } - qfile = vstream_fdopen(fd, O_RDONLY); - - /* * Contact the cleanup service and read the queue ID that it has * allocated. In case of trouble, request that the cleanup service * bounces its copy of the message. because the original input file is @@ -363,10 +352,13 @@ static int pickup_file(PICKUP_INFO *info) */ #define PICKUP_CLEANUP_FLAGS (CLEANUP_FLAG_BOUNCE | CLEANUP_FLAG_FILTER) - buf = vstring_alloc(100); - cleanup = mail_connect_wait(MAIL_CLASS_PRIVATE, MAIL_SERVICE_CLEANUP); - if (mail_scan(cleanup, "%s", buf) != 1 - || mail_print(cleanup, "%d", PICKUP_CLEANUP_FLAGS) != 0) { + cleanup = mail_connect_wait(MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP); + if (attr_scan(cleanup, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, buf, + ATTR_TYPE_END) != 1 + || attr_print(cleanup, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, PICKUP_CLEANUP_FLAGS, + ATTR_TYPE_END) != 0) { status = KEEP_MESSAGE_FILE; } else { info->id = mystrdup(vstring_str(buf)); @@ -445,11 +437,16 @@ static void pickup_service(char *unused_buf, int unused_len, } while (file_count); } -/* drop_privileges - drop privileges most of the time */ +/* drop_privileges - drop privileges */ static void drop_privileges(char *unused_name, char **unused_argv) { - set_eugid(var_owner_uid, var_owner_gid); + + /* + * In case master.cf was not updated for unprivileged service. + */ + if (getuid() != var_owner_uid) + set_ugid(var_owner_uid, var_owner_gid); } /* main - pass control to the multi-threaded server skeleton */ diff --git a/gnu/dist/postfix/src/pipe/Makefile.in b/gnu/dist/postfix/src/pipe/Makefile.in index 8575f30fbd4..c46d22ca03d 100644 --- a/gnu/dist/postfix/src/pipe/Makefile.in +++ b/gnu/dist/postfix/src/pipe/Makefile.in @@ -82,4 +82,5 @@ pipe.o: ../../include/mail_addr.h pipe.o: ../../include/canon_addr.h pipe.o: ../../include/split_addr.h pipe.o: ../../include/off_cvt.h +pipe.o: ../../include/quote_822_local.h pipe.o: ../../include/mail_server.h diff --git a/gnu/dist/postfix/src/pipe/pipe.c b/gnu/dist/postfix/src/pipe/pipe.c index 2b1552c60c1..9794c697cfc 100644 --- a/gnu/dist/postfix/src/pipe/pipe.c +++ b/gnu/dist/postfix/src/pipe/pipe.c @@ -11,6 +11,10 @@ /* This program expects to be run from the \fBmaster\fR(8) process /* manager. /* +/* Message attributes such as sender address, recipient address and +/* next-hop host name can be specified as command-line macros that are +/* expanded before the external command is executed. +/* /* The \fBpipe\fR daemon updates queue files and marks recipients /* as finished, or it informs the queue manager that delivery should /* be tried again at a later time. Delivery problem reports are sent @@ -23,10 +27,10 @@ /* fax machines, and so on. /* /* To prevent Postfix from sending multiple recipients per delivery -/* request, specify +/* request, specify /* /* .ti +4 -/* \fItransport\fB_destination_recipient_limit = 1\fR +/* \fItransport\fB_destination_recipient_limit = 1\fR /* /* in the Postfix \fBmain.cf\fR file, where \fItransport\fR /* is the name in the first column of the Postfix \fBmaster.cf\fR @@ -36,7 +40,7 @@ /* .fi /* The external command attributes are given in the \fBmaster.cf\fR /* file at the end of a service definition. The syntax is as follows: -/* .IP "\fBflags=BFR.>\fR (optional)" +/* .IP "\fBflags=BFRhqu.>\fR (optional)" /* Optional message processing flags. By default, a message is /* copied unchanged. /* .RS @@ -51,6 +55,27 @@ /* .IP \fBR\fR /* Prepend a \fBReturn-Path:\fR message header with the envelope sender /* address. +/* .IP \fBh\fR +/* Fold the command-line \fB$recipient\fR domain name and \fB$nexthop\fR +/* host name to lower case. +/* This is recommended for delivery via \fBUUCP\fR. +/* .IP \fBq\fR +/* Quote white space and other special characters in the command-line +/* \fB$sender\fR and \fB$recipient\fR address localparts (text to the +/* left of the right-most \fB@\fR character), according to an 8-bit +/* transparent version of RFC 822. +/* This is recommended for delivery via \fBUUCP\fR or \fBBSMTP\fR. +/* .sp +/* The result is compatible with the address parsing of command-line +/* recipients by the Postfix \fBsendmail\fR mail submission command. +/* .sp +/* The \fBq\fR flag affects only entire addresses, not the partial +/* address information from the \fB$user\fR, \fB$extension\fR or +/* \fB$mailbox\fR command-line macros. +/* .IP \fBu\fR +/* Fold the command-line \fB$recipient\fR address localpart (text to +/* the left of the right-most \fB@\fR character) to lower case. +/* This is recommended for delivery via \fBUUCP\fR. /* .IP \fB.\fR /* Prepend \fB.\fR to lines starting with "\fB.\fR". This is needed /* by, for example, \fBBSMTP\fR software. @@ -91,6 +116,8 @@ /* .sp /* A command-line argument that contains \fB${\fBextension\fR}\fR expands /* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. /* .IP \fB${\fBmailbox\fR}\fR /* This macro expands to the complete local part of a recipient address. /* For example, with an address \fIuser+foo@domain\fR the mailbox is @@ -98,15 +125,24 @@ /* .sp /* A command-line argument that contains \fB${\fBmailbox\fR}\fR /* expands into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. /* .IP \fB${\fBnexthop\fR}\fR /* This macro expands to the next-hop hostname. +/* .sp +/* This information is modified by the \fBh\fR flag for case folding. /* .IP \fB${\fBrecipient\fR}\fR /* This macro expands to the complete recipient address. /* .sp /* A command-line argument that contains \fB${\fBrecipient\fR}\fR /* expands into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBhqu\fR flags for quoting +/* and case folding. /* .IP \fB${\fBsender\fR}\fR /* This macro expands to the envelope sender address. +/* .sp +/* This information is modified by the \fBq\fR flag for quoting. /* .IP \fB${\fBsize\fR}\fR /* This macro expands to Postfix's idea of the message size, which /* is an approximation of the size of the message as delivered. @@ -117,6 +153,8 @@ /* .sp /* A command-line argument that contains \fB${\fBuser\fR}\fR expands /* into as many command-line arguments as there are recipients. +/* .sp +/* This information is modified by the \fBu\fR flag for case folding. /* .RE /* .PP /* In addition to the form ${\fIname\fR}, the forms $\fIname\fR and @@ -195,6 +233,7 @@ #include <pwd.h> #include <grp.h> #include <fcntl.h> +#include <ctype.h> #ifdef STRCASECMP_IN_STRINGS_H #include <strings.h> @@ -231,6 +270,7 @@ #include <canon_addr.h> #include <split_addr.h> #include <off_cvt.h> +#include <quote_822_local.h> /* Single server skeleton. */ @@ -262,6 +302,16 @@ #define PIPE_FLAG_SIZE (1<<4) /* + * Additional flags. These are colocated with mail_copy() flags. Allow some + * space for extension of the mail_copy() interface. + */ +#define PIPE_OPT_FOLD_USER (1<<16) +#define PIPE_OPT_FOLD_HOST (1<<17) +#define PIPE_OPT_QUOTE_LOCAL (1<<18) + +#define PIPE_OPT_FOLD_FLAGS (PIPE_OPT_FOLD_USER | PIPE_OPT_FOLD_HOST) + + /* * Tunable parameters. Values are taken from the config file, after * prepending the service name to _name, and so on. */ @@ -291,6 +341,11 @@ typedef struct { off_t size_limit; /* max size in bytes we will accept */ } PIPE_ATTR; + /* + * Silly little macros. + */ +#define STR vstring_str + /* parse_callback - callback for mac_parse() */ static int parse_callback(int type, VSTRING *buf, char *context) @@ -313,9 +368,44 @@ static int parse_callback(int type, VSTRING *buf, char *context) return (0); } +/* morph_recipient - morph a recipient address */ + +static void morph_recipient(VSTRING *buf, const char *address, int flags) +{ + char *cp; + + /* + * Quote the recipient address as appropriate. + */ + if (flags & PIPE_OPT_QUOTE_LOCAL) + quote_822_local(buf, address); + else + vstring_strcpy(buf, address); + + /* + * Fold the recipient address as appropriate. + */ + switch (flags & PIPE_OPT_FOLD_FLAGS) { + case PIPE_OPT_FOLD_HOST: + if ((cp = strrchr(STR(buf), '@')) != 0) + lowercase(cp + 1); + break; + case PIPE_OPT_FOLD_USER: + if ((cp = strrchr(STR(buf), '@')) != 0) { + *cp = 0; + lowercase(STR(buf)); + *cp = '@'; + break; + } + case PIPE_OPT_FOLD_USER | PIPE_OPT_FOLD_HOST: + lowercase(STR(buf)); + break; + } +} + /* expand_argv - expand macros in the argument vector */ -static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) +static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list, int flags) { VSTRING *buf = vstring_alloc(100); ARGV *result; @@ -338,7 +428,6 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) * would screw up mail addresses that contain $ characters. */ #define NO 0 -#define STR vstring_str result = argv_alloc(1); for (cpp = argv; *cpp; cpp++) { @@ -353,8 +442,8 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) * This argument contains $recipient. */ if (expand_flag & PIPE_FLAG_RCPT) { - dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, - rcpt_list->info[i].address); + morph_recipient(buf, rcpt_list->info[i].address, flags); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_RCPT, STR(buf)); } /* @@ -370,7 +459,8 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) * expansions of this specific command-line argument. */ if (expand_flag & PIPE_FLAG_USER) { - vstring_strcpy(buf, rcpt_list->info[i].address); + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_FLAGS); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); @@ -378,7 +468,6 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) split_addr(STR(buf), *var_rcpt_delim); if (*STR(buf) == 0) continue; - lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); } @@ -388,15 +477,14 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) * delimiter and the rightmost @. The extension may be blank. */ if (expand_flag & PIPE_FLAG_EXTENSION) { - vstring_strcpy(buf, rcpt_list->info[i].address); + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_FLAGS); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); if (*var_rcpt_delim == 0 || (ext = split_addr(STR(buf), *var_rcpt_delim)) == 0) ext = ""; /* insert null arg */ - else - lowercase(ext); dict_update(PIPE_DICT_TABLE, PIPE_DICT_EXTENSION, ext); } @@ -405,11 +493,11 @@ static ARGV *expand_argv(char **argv, RECIPIENT_LIST *rcpt_list) * anything to the left of the rightmost @. */ if (expand_flag & PIPE_FLAG_MAILBOX) { - vstring_strcpy(buf, rcpt_list->info[i].address); + morph_recipient(buf, rcpt_list->info[i].address, + flags & PIPE_OPT_FOLD_FLAGS); if (split_at_right(STR(buf), '@') == 0) msg_warn("no @ in recipient address: %s", rcpt_list->info[i].address); - lowercase(STR(buf)); dict_update(PIPE_DICT_TABLE, PIPE_DICT_MAILBOX, STR(buf)); } @@ -492,6 +580,15 @@ static void get_service_attr(PIPE_ATTR *attr, char **argv) case 'R': attr->flags |= MAIL_COPY_RETURN_PATH; break; + case 'h': + attr->flags |= PIPE_OPT_FOLD_HOST; + break; + case 'q': + attr->flags |= PIPE_OPT_QUOTE_LOCAL; + break; + case 'u': + attr->flags |= PIPE_OPT_FOLD_USER; + break; default: msg_fatal("unknown flag: %c (ignored)", *cp); break; @@ -620,6 +717,9 @@ static int eval_command_status(int command_status, char *service, service, request->arrival_time, "%s", why); } break; + case PIPE_STAT_CORRUPT: + result |= DEL_STAT_DEFER; + break; default: msg_panic("eval_command_status: bad status %d", command_status); /* NOTREACHED */ @@ -704,13 +804,22 @@ static int deliver_message(DELIVER_REQUEST *request, char *service, char **argv) if (vstream_fseek(request->fp, request->data_offset, SEEK_SET) < 0) msg_fatal("seek queue file %s: %m", VSTREAM_PATH(request->fp)); - dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, request->sender); - dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); buf = vstring_alloc(10); + if (attr.flags & PIPE_OPT_QUOTE_LOCAL) { + quote_822_local(buf, request->sender); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_SENDER, request->sender); + if (attr.flags & PIPE_OPT_FOLD_HOST) { + vstring_strcpy(buf, request->nexthop); + lowercase(STR(buf)); + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, STR(buf)); + } else + dict_update(PIPE_DICT_TABLE, PIPE_DICT_NEXTHOP, request->nexthop); vstring_sprintf(buf, "%ld", (long) request->data_size); dict_update(PIPE_DICT_TABLE, PIPE_DICT_SIZE, STR(buf)); vstring_free(buf); - expanded_argv = expand_argv(attr.command, rcpt_list); + expanded_argv = expand_argv(attr.command, rcpt_list, attr.flags); export_env = argv_split(var_export_environ, ", \t\r\n"); command_status = pipe_command(request->fp, why, diff --git a/gnu/dist/postfix/src/postalias/Makefile.in b/gnu/dist/postfix/src/postalias/Makefile.in index 1fbc2a67d54..b18759656dd 100644 --- a/gnu/dist/postfix/src/postalias/Makefile.in +++ b/gnu/dist/postfix/src/postalias/Makefile.in @@ -21,10 +21,36 @@ $(PROG): $(OBJS) $(LIBS) Makefile: Makefile.in (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ -test: $(TESTPROG) - update: ../../bin/$(PROG) +test: test1 test2 + +test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-ABC1.ref + ./$(PROG) map.in + for key in abc ghi; \ + do \ + ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ + done + ./$(PROG) -f map.in + for key in ABC; \ + do \ + ./$(PROG) -fq $${key} map.in | diff map-$${key}1.ref -; \ + done + rm -f map.in.db + +test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-ABC2.ref + ./$(PROG) map.in + for key in abc ghi; \ + do \ + echo $${key} | ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ + done + ./$(PROG) -f map.in + for key in ABC; \ + do \ + echo $${key} | ./$(PROG) -fq - map.in | diff map-$${key}2.ref -; \ + done + rm -f map.in.db + ../../bin/$(PROG): $(PROG) cp $(PROG) ../../bin @@ -65,6 +91,7 @@ postalias.o: ../../include/readlline.h postalias.o: ../../include/stringops.h postalias.o: ../../include/split_at.h postalias.o: ../../include/get_hostname.h +postalias.o: ../../include/vstring_vstream.h postalias.o: ../../include/tok822.h postalias.o: ../../include/resolve_clnt.h postalias.o: ../../include/mail_conf.h diff --git a/gnu/dist/postfix/src/postalias/map-ABC1.ref b/gnu/dist/postfix/src/postalias/map-ABC1.ref new file mode 100644 index 00000000000..142195f36ae --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-ABC1.ref @@ -0,0 +1 @@ +DEF diff --git a/gnu/dist/postfix/src/postalias/map-ABC2.ref b/gnu/dist/postfix/src/postalias/map-ABC2.ref new file mode 100644 index 00000000000..929916bcb4d --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-ABC2.ref @@ -0,0 +1 @@ +ABC: DEF diff --git a/gnu/dist/postfix/src/postalias/map-abc1.ref b/gnu/dist/postfix/src/postalias/map-abc1.ref new file mode 100644 index 00000000000..142195f36ae --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-abc1.ref @@ -0,0 +1 @@ +DEF diff --git a/gnu/dist/postfix/src/postalias/map-abc2.ref b/gnu/dist/postfix/src/postalias/map-abc2.ref new file mode 100644 index 00000000000..dfbdab6f18e --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-abc2.ref @@ -0,0 +1 @@ +abc: DEF diff --git a/gnu/dist/postfix/src/postalias/map-ghi1.ref b/gnu/dist/postfix/src/postalias/map-ghi1.ref new file mode 100644 index 00000000000..7beb1db20dc --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-ghi1.ref @@ -0,0 +1 @@ +jkl diff --git a/gnu/dist/postfix/src/postalias/map-ghi2.ref b/gnu/dist/postfix/src/postalias/map-ghi2.ref new file mode 100644 index 00000000000..e2ca310ca9d --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map-ghi2.ref @@ -0,0 +1 @@ +ghi: jkl diff --git a/gnu/dist/postfix/src/postalias/map.in b/gnu/dist/postfix/src/postalias/map.in new file mode 100644 index 00000000000..203fa0cd34a --- /dev/null +++ b/gnu/dist/postfix/src/postalias/map.in @@ -0,0 +1,2 @@ +ABC: DEF +ghi: jkl diff --git a/gnu/dist/postfix/src/postalias/postalias.c b/gnu/dist/postfix/src/postalias/postalias.c index 0fe46f910f5..6daf0fff6d4 100644 --- a/gnu/dist/postfix/src/postalias/postalias.c +++ b/gnu/dist/postfix/src/postalias/postalias.c @@ -5,7 +5,7 @@ /* Postfix alias database maintenance /* SYNOPSIS /* .fi -/* \fBpostalias\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR] +/* \fBpostalias\fR [\fB-Nfinrvw\fR] [\fB-c \fIconfig_dir\fR] /* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] /* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION @@ -29,7 +29,14 @@ /* instead of the default configuration directory. /* .IP "\fB-d \fIkey\fR" /* Search the specified maps for \fIkey\fR and remove one entry per map. -/* The exit status is non-zero if the requested information was not found. +/* The exit status is zero when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream. The exit status is zero +/* when at least one of the requested keys was found. +/* .IP \fB-f\fR +/* Do not fold the lookup key to lower case while creating or querying +/* a map. /* .IP \fB-i\fR /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostalias\fR creates @@ -40,8 +47,13 @@ /* the host operating system. /* .IP "\fB-q \fIkey\fR" /* Search the specified maps for \fIkey\fR and print the first value -/* found on the standard output stream. The exit status is non-zero -/* if the requested information was not found. +/* found on the standard output stream. The exit status is zero +/* when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream and prints one line of +/* \fIkey: value\fR output for each key that was found. The exit +/* status is zero when at least one of the requested keys was found. /* .IP \fB-r\fR /* When updating a table, do not warn about duplicate entries; silently /* replace them. @@ -81,9 +93,6 @@ /* \fBpostalias\fR terminates with zero exit status in case of success /* (including successful \fBpostmap -q\fR lookup) and terminates /* with non-zero exit status in case of failure. -/* BUGS -/* The "delete key" support is limited to one delete operation -/* per command invocation. /* ENVIRONMENT /* .ad /* .fi @@ -137,6 +146,7 @@ #include <stringops.h> #include <split_at.h> #include <get_hostname.h> +#include <vstring_vstream.h> /* Global library. */ @@ -188,23 +198,7 @@ static void postalias(char *map_type, char *path_name, * Add records to the database. */ lineno = 0; - while (readlline(line_buffer, source_fp, &lineno, READLL_STRIPNL)) { - - /* - * Skip comments. - */ - if (*STR(line_buffer) == '#') - continue; - - /* - * Weird stuff. Normally, a line that begins with whitespace is a - * continuation of the previous line. - */ - if (ISSPACE(*STR(line_buffer))) { - msg_warn("%s, line %d: malformed line", - VSTREAM_PATH(source_fp), lineno); - continue; - } + while (readlline(line_buffer, source_fp, &lineno)) { /* * Tokenize the input, so that we do the right thing when a quoted @@ -263,7 +257,8 @@ static void postalias(char *map_type, char *path_name, /* * Store the value under a case-insensitive key. */ - lowercase(STR(key_buffer)); + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(STR(key_buffer)); mkmap_append(mkmap, STR(key_buffer), STR(value_buffer)); } @@ -306,6 +301,65 @@ static void postalias(char *map_type, char *path_name, vstream_fclose(source_fp); } +/* postalias_queries - apply multiple requests from stdin */ + +static int postalias_queries(VSTREAM *in, char **maps, const int map_count, + const int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + const char *value; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postalias_queries: bad map count"); + + /* + * Prepare to open maps lazily. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = 0; + + /* + * Perform all queries. Open maps on the fly, to avoid opening unecessary + * maps. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(STR(keybuf)); + for (n = 0; n < map_count; n++) { + if (dicts[n] == 0) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDONLY, DICT_FLAG_LOCK) : + dict_open3(var_db_type, maps[n], O_RDONLY, DICT_FLAG_LOCK)); + if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { + vstream_printf("%s: %s\n", STR(keybuf), value); + found = 1; + break; + } + } + } + if (found) + vstream_fflush(VSTREAM_OUT); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((char *) dicts); + vstring_free(keybuf); + + return (found); +} + /* postalias_query - query a map and print the result to stdout */ static int postalias_query(const char *map_type, const char *map_name, @@ -323,6 +377,50 @@ static int postalias_query(const char *map_type, const char *map_name, return (value != 0); } +/* postalias_deletes - apply multiple requests from stdin */ + +static int postalias_deletes(VSTREAM *in, char **maps, const int map_count) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postalias_deletes: bad map count"); + + /* + * Open maps ahead of time. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDWR, DICT_FLAG_LOCK) : + dict_open3(var_db_type, maps[n], O_RDWR, DICT_FLAG_LOCK)); + + /* + * Perform all requests. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) + for (n = 0; n < map_count; n++) + found |= (dict_del(dicts[n], STR(keybuf)) == 0); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((char *) dicts); + vstring_free(keybuf); + + return (found); +} + /* postalias_delete - delete a key value pair from a map */ static int postalias_delete(const char *map_type, const char *map_name, @@ -331,10 +429,6 @@ static int postalias_delete(const char *map_type, const char *map_name, DICT *dict; int status; - /* - * XXX This must be generalized to multi-key (read from stdin) and - * multi-map (given on command line) updates. - */ dict = dict_open3(map_type, map_name, O_RDWR, DICT_FLAG_LOCK); status = dict_del(dict, key); dict_close(dict); @@ -345,7 +439,7 @@ static int postalias_delete(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-Ninrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-Nfinrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", myname); } @@ -357,7 +451,7 @@ int main(int argc, char **argv) char *slash; struct stat st; int open_flags = O_RDWR | O_CREAT | O_TRUNC; - int dict_flags = DICT_FLAG_DUP_WARN; + int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_KEY; char *query = 0; char *delkey = 0; int found; @@ -395,7 +489,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "Nc:d:inq:rvw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:d:finq:rvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -413,6 +507,9 @@ int main(int argc, char **argv) msg_fatal("specify only one of -q or -d"); delkey = optarg; break; + case 'f': + dict_flags &= ~DICT_FLAG_FOLD_KEY; + break; case 'i': open_flags &= ~O_TRUNC; break; @@ -447,6 +544,8 @@ int main(int argc, char **argv) if (delkey) { /* remove entry */ if (optind + 1 > argc) usage(argv[0]); + if (strcmp(delkey, "-") == 0) + exit(postalias_deletes(VSTREAM_IN, argv + optind, argc - optind) == 0); found = 0; while (optind < argc) { if ((path_name = split_at(argv[optind], ':')) != 0) { @@ -460,6 +559,11 @@ int main(int argc, char **argv) } else if (query) { /* query map(s) */ if (optind + 1 > argc) usage(argv[0]); + if (strcmp(query, "-") == 0) + exit(postalias_queries(VSTREAM_IN, argv + optind, argc - optind, + dict_flags) == 0); + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(query); while (optind < argc) { if ((path_name = split_at(argv[optind], ':')) != 0) { found = postalias_query(argv[optind], path_name, query); diff --git a/gnu/dist/postfix/src/postconf/Makefile.in b/gnu/dist/postfix/src/postconf/Makefile.in index 1cf46772319..35434d4b751 100644 --- a/gnu/dist/postfix/src/postconf/Makefile.in +++ b/gnu/dist/postfix/src/postconf/Makefile.in @@ -23,7 +23,10 @@ $(PROG): $(OBJS) $(LIBS) ../../conf/main.cf.default: $(PROG) Makefile rm -f $@ - ./$(PROG) -d |egrep -v '^(myhostname|mydomain|mynetworks) ' >$@ + (echo "# DO NOT EDIT THIS FILE. EDIT THE MAIN.CF FILE INSTEAD. THE"; \ + echo "# TEXT HERE JUST SHOWS DEFAULT SETTINGS BUILT INTO POSTFIX."; \ + echo "#"; \ + ./$(PROG) -d) |egrep -v '^(myhostname|mydomain|mynetworks) ' >$@ Makefile: Makefile.in (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ @@ -35,7 +38,7 @@ update: ../../bin/$(PROG) $(SAMPLES) ../../bin/$(PROG): $(PROG) cp $(PROG) ../../bin -$(MAKES): $(INC_DIR)/mail_params.h +$(MAKES): $(INC_DIR)/mail_params.h ../global/mail_params.c $(AWK) -f extract.awk ../*/*.c printfck: $(OBJS) $(PROG) @@ -79,12 +82,13 @@ postconf.o: ../../include/argv.h postconf.o: ../../include/safe.h postconf.o: ../../include/mymalloc.h postconf.o: ../../include/split_at.h -postconf.o: ../../include/readlline.h +postconf.o: ../../include/vstring_vstream.h postconf.o: ../../include/myflock.h postconf.o: ../../include/mynetworks.h postconf.o: ../../include/mail_conf.h postconf.o: ../../include/mail_proto.h postconf.o: ../../include/iostuff.h +postconf.o: ../../include/attr.h postconf.o: ../../include/mail_version.h postconf.o: ../../include/mail_params.h postconf.o: ../../include/mail_addr.h @@ -95,9 +99,11 @@ postconf.o: int_vars.h postconf.o: str_vars.h postconf.o: raw_vars.h postconf.o: smtp_vars.h +postconf.o: install_vars.h postconf.o: time_table.h postconf.o: bool_table.h postconf.o: int_table.h postconf.o: str_table.h postconf.o: smtp_table.h +postconf.o: install_table.h postconf.o: raw_table.h diff --git a/gnu/dist/postfix/src/postconf/install_table.h b/gnu/dist/postfix/src/postconf/install_table.h new file mode 100644 index 00000000000..a7dff6f3f18 --- /dev/null +++ b/gnu/dist/postfix/src/postconf/install_table.h @@ -0,0 +1 @@ + VAR_CONFIG_DIR, DEF_CONFIG_DIR, &var_config_dir, 1, 0, diff --git a/gnu/dist/postfix/src/postconf/install_vars.h b/gnu/dist/postfix/src/postconf/install_vars.h new file mode 100644 index 00000000000..c54581ee8ee --- /dev/null +++ b/gnu/dist/postfix/src/postconf/install_vars.h @@ -0,0 +1 @@ +char *var_config_dir; diff --git a/gnu/dist/postfix/src/postconf/postconf.c b/gnu/dist/postfix/src/postconf/postconf.c index da7d5625edd..ffcc7fbd86c 100644 --- a/gnu/dist/postfix/src/postconf/postconf.c +++ b/gnu/dist/postfix/src/postconf/postconf.c @@ -84,7 +84,7 @@ #include <mymalloc.h> #include <argv.h> #include <split_at.h> -#include <readlline.h> +#include <vstring_vstream.h> #include <myflock.h> /* Global library. */ @@ -131,6 +131,7 @@ DICT *text_table; * Manually extracted. */ #include "smtp_vars.h" +#include "install_vars.h" /* * Lookup tables generated by scanning actual C source files. @@ -153,6 +154,7 @@ static CONFIG_INT_TABLE int_table[] = { static CONFIG_STR_TABLE str_table[] = { #include "str_table.h" #include "smtp_table.h" /* XXX */ +#include "install_table.h" 0, }; @@ -225,7 +227,7 @@ static void get_myhostname(void) { const char *name; - if ((name = dict_lookup(CONFIG_DICT, VAR_MYHOSTNAME)) == 0) + if ((name = mail_conf_lookup_eval(VAR_MYHOSTNAME)) == 0) name = check_myhostname(); var_myhostname = mystrdup(name); } @@ -255,13 +257,13 @@ static const char *check_mynetworks(void) if (var_inet_interfaces == 0) { if ((mode & SHOW_DEFS) - || !(junk = mail_conf_lookup(VAR_INET_INTERFACES))) + || !(junk = mail_conf_lookup_eval(VAR_INET_INTERFACES))) junk = DEF_INET_INTERFACES; var_inet_interfaces = mystrdup(junk); } if (var_mynetworks_style == 0) { if ((mode & SHOW_DEFS) - || !(junk = mail_conf_lookup(VAR_MYNETWORKS_STYLE))) + || !(junk = mail_conf_lookup_eval(VAR_MYNETWORKS_STYLE))) junk = DEF_MYNETWORKS_STYLE; var_mynetworks_style = mystrdup(junk); } @@ -280,9 +282,9 @@ static void edit_parameters(int argc, char **argv) VSTRING *buf = vstring_alloc(100); VSTRING *key = vstring_alloc(10); char *cp; - char *value; + char *edit_key; + char *edit_val; HTABLE *table; - int first = 1; struct cvalue { char *value; int found; @@ -290,21 +292,26 @@ static void edit_parameters(int argc, char **argv) struct cvalue *cvalue; HTABLE_INFO **ht_info; HTABLE_INFO **ht; + int interesting; + const char *err; /* * Store command-line parameters for quick lookup. */ table = htable_create(argc); while ((cp = *argv++) != 0) { - if ((value = split_at(cp, '=')) == 0 - || *(cp += strspn(cp, " \t\r\n")) == 0) - msg_fatal("edit requires \"key = value\" arguments"); - while (*value && ISSPACE(*value)) - value++; + if (strchr(cp, '\n') != 0) + msg_fatal("edit accepts no multi-line input"); + while (ISSPACE(*cp)) + cp++; + if (*cp == '#') + msg_fatal("edit accepts no comment input"); + if ((err = split_nameval(cp, &edit_key, &edit_val)) != 0) + msg_fatal("%s: \"%s\"", err, cp); cvalue = (struct cvalue *) mymalloc(sizeof(*cvalue)); - cvalue->value = value; + cvalue->value = edit_val; cvalue->found = 0; - htable_enter(table, mystrtok(&cp, " \t\r\n"), (char *) cvalue); + htable_enter(table, edit_key, (char *) cvalue); } /* @@ -342,26 +349,30 @@ static void edit_parameters(int argc, char **argv) */ #define STR(x) vstring_str(x) - while (readlline(buf, src, (int *) 0, READLL_KEEPNL)) { - cp = STR(buf); - if (first) { - first = 0; - if (ISSPACE(*cp)) - msg_fatal("%s: file starts with whitespace", path); - } - if (*cp == '#') { + interesting = 0; + while (vstring_get(buf, src) != VSTREAM_EOF) { + for (cp = STR(buf); ISSPACE(*cp) /* including newline */ ; cp++) + /* void */ ; + /* Copy comment, all-whitespace, or empty line. */ + if (*cp == '#' || *cp == 0) { vstream_fputs(STR(buf), dst); - continue; } - cp += strspn(cp, " \t\r\n"); - vstring_strncpy(key, cp, strcspn(cp, " \t\r\n=")); - cvalue = (struct cvalue *) htable_find(table, STR(key)); - if (cvalue == 0) { - vstream_fputs(STR(buf), dst); - } else { - if (cvalue->found++ == 1) - msg_warn("%s: multiple entries for key %s", path, STR(key)); - vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); + /* Copy or skip continued text. */ + else if (cp > STR(buf)) { + if (interesting == 0) + vstream_fputs(STR(buf), dst); + } + /* Copy or replace start of logical line. */ + else { + vstring_strncpy(key, cp, strcspn(cp, " \t\r\n=")); + cvalue = (struct cvalue *) htable_find(table, STR(key)); + if ((interesting = !!cvalue) != 0) { + if (cvalue->found++ == 1) + msg_warn("%s: multiple entries for \"%s\"", path, STR(key)); + vstream_fprintf(dst, "%s = %s\n", STR(key), cvalue->value); + } else { + vstream_fputs(STR(buf), dst); + } } } diff --git a/gnu/dist/postfix/src/postdrop/Makefile.in b/gnu/dist/postfix/src/postdrop/Makefile.in index 2d893c3ac61..a58a1b581ce 100644 --- a/gnu/dist/postfix/src/postdrop/Makefile.in +++ b/gnu/dist/postfix/src/postdrop/Makefile.in @@ -63,8 +63,9 @@ postdrop.o: ../../include/vstring.h postdrop.o: ../../include/msg_vstream.h postdrop.o: ../../include/msg_syslog.h postdrop.o: ../../include/argv.h -postdrop.o: ../../include/mail_proto.h postdrop.o: ../../include/iostuff.h +postdrop.o: ../../include/mail_proto.h +postdrop.o: ../../include/attr.h postdrop.o: ../../include/mail_queue.h postdrop.o: ../../include/mail_params.h postdrop.o: ../../include/mail_conf.h diff --git a/gnu/dist/postfix/src/postdrop/postdrop.c b/gnu/dist/postfix/src/postdrop/postdrop.c index 4a8c16d3c48..dacd70fdee5 100644 --- a/gnu/dist/postfix/src/postdrop/postdrop.c +++ b/gnu/dist/postfix/src/postdrop/postdrop.c @@ -2,29 +2,31 @@ /* NAME /* postdrop 1 /* SUMMARY -/* Postfix mail posting agent +/* Postfix mail posting utility /* SYNOPSIS -/* \fBpostdrop\fR [\fIoption ...\fR] +/* \fBpostdrop\fR [\fB-rv\fR] [\fB-c \fIconfig_dir\fR] /* DESCRIPTION /* The \fBpostdrop\fR command creates a file in the \fBmaildrop\fR /* directory and copies its standard input to the file. /* -/* The command is designed to run with set-gid privileges, and with -/* group write permission to the \fBmaildrop\fR queue directory. -/* -/* The \fBpostdrop\fR command is automatically invoked by the -/* \fBsendmail\fR(1) mail posting agent when the \fBmaildrop\fR -/* queue directory is not world-writable. -/* /* Options: +/* .IP \fB-c \fIconfig_dir\fR +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. See also the +/* MAIL_CONFIG environment setting below. +/* .IP \fB-r\fR +/* Use a Postfix-internal protocol for reading the message from +/* standard input, and for reporting status information on standard +/* output. This is currently the only supported method. /* .IP \fB-v\fR /* Enable verbose logging for debugging purposes. Multiple \fB-v\fR /* options make the software increasingly verbose. /* SECURITY /* .ad /* .fi -/* This program is designed so that it can run with set-user (or -/* group) id privileges. +/* The command is designed to run with set-group ID privileges, so +/* that it can write to the \fBmaildrop\fR queue directory and so that +/* it can connect to Postfix daemon processes. /* DIAGNOSTICS /* Fatal errors: malformed input, I/O error, out of memory. Problems /* are logged to \fBsyslogd\fR(8) and to the standard error stream. @@ -33,8 +35,16 @@ /* ENVIRONMENT /* .ad /* .fi -/* The program deletes all environment information, because the C -/* library can't be trusted. +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. In order to avoid exploitation +/* of set-group ID privileges, it is not possible to specify arbitrary +/* directory names. +/* +/* A non-standard directory is allowed only if the name is listed in the +/* standard \fBmain.cf\fR file, in the \fBalternate_config_directories\fR +/* configuration parameter value. +/* +/* Only the super-user is allowed to specify arbitrary directory names. /* FILES /* /var/spool/postfix, mail queue /* /etc/postfix, configuration files @@ -75,6 +85,7 @@ #include <stdlib.h> #include <signal.h> #include <syslog.h> +#include <errno.h> /* Utility library. */ @@ -85,6 +96,7 @@ #include <msg_vstream.h> #include <msg_syslog.h> #include <argv.h> +#include <iostuff.h> /* Global library. */ @@ -104,10 +116,14 @@ /* * WARNING WARNING WARNING * - * This software is designed to run set-gid on systems that cannot afford a - * world-writable spool directory. In order to make this restriction work, - * this software should not run any external commands, nor should it take - * any configuration information from the user. + * This software is designed to run set-gid. In order to avoid exploitation of + * privilege, this software should not run any external commands, nor should + * it take any information from the user unless that information can be + * properly sanitized. To get an idea of how much information a process can + * inherit from a potentially hostile user, examine all the members of the + * process structure (typically, in /usr/include/sys/proc.h): the current + * directory, open files, timers, signals, environment, command line, umask, + * and so on. */ /* @@ -137,8 +153,17 @@ static void postdrop_cleanup(void) static void postdrop_sig(int sig) { - postdrop_cleanup(); - exit(sig); + + /* + * Assume atomic signal() updates, even when emulated with sigaction(). + */ + if (signal(SIGHUP, SIG_IGN) != SIG_IGN + && signal(SIGINT, SIG_IGN) != SIG_IGN + && signal(SIGQUIT, SIG_IGN) != SIG_IGN + && signal(SIGTERM, SIG_IGN) != SIG_IGN) { + postdrop_cleanup(); + exit(sig); + } } /* main - the main program */ @@ -183,6 +208,28 @@ int main(int argc, char **argv) set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); /* + * Parse JCL. This program is set-gid and must sanitize all command-line + * arguments. 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:rv")) > 0) { + switch (c) { + case 'c': + if (setenv(CONF_ENV_PATH, optarg, 1) < 0) + msg_fatal("out of memory"); + break; + case 'r': /* forward compatibility */ + break; + case 'v': + msg_verbose++; + break; + default: + msg_fatal("usage: %s [-c config_dir] [-v]", argv[0]); + } + } + + /* * Read the global configuration file and extract configuration * information. Some claim that the user should supply the working * directory instead. That might be OK, given that this command needs @@ -194,6 +241,13 @@ int main(int argc, char **argv) mail_conf_read(); /* + * Stop run-away process accidents by limiting the queue file size. This + * is not a defense against DOS attack. + */ + if (var_message_limit > 0 && get_file_limit() > var_message_limit) + set_file_limit((off_t) var_message_limit); + + /* * Strip the environment so we don't have to trust the C library. */ import_env = argv_split(var_import_environ, ", \t\r\n"); @@ -210,6 +264,7 @@ int main(int argc, char **argv) * clean up incomplete output. */ signal(SIGPIPE, SIG_IGN); + signal(SIGXFSZ, SIG_IGN); signal(SIGHUP, postdrop_sig); signal(SIGINT, postdrop_sig); @@ -217,18 +272,7 @@ int main(int argc, char **argv) signal(SIGTERM, postdrop_sig); msg_cleanup(postdrop_cleanup); - /* - * Parse JCL. - */ - while ((c = GETOPT(argc, argv, "v")) > 0) { - switch (c) { - case 'v': - msg_verbose++; - break; - default: - msg_fatal("usage: %s [-v]", argv[0]); - } - } + /* End of initializations. */ /* * Create queue file. mail_stream_file() never fails. Send the queue ID @@ -236,8 +280,10 @@ int main(int argc, char **argv) * clean up in case of a fatal error or an interrupt. */ dst = mail_stream_file(MAIL_QUEUE_MAILDROP, MAIL_CLASS_PUBLIC, - MAIL_SERVICE_PICKUP); - mail_print(VSTREAM_OUT, "%s", dst->id); + MAIL_SERVICE_PICKUP, 0444); + attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, dst->id, + ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); postdrop_path = mystrdup(VSTREAM_PATH(dst->stream)); @@ -248,6 +294,9 @@ int main(int argc, char **argv) * file descriptor to the cleanup daemon. These are by no means all * sanity checks - the cleanup service and queue manager services will * reject messages that lack required information. + * + * If something goes wrong, slurp up the input before responding to the + * client, otherwise the client will give up after detecting SIGPIPE. */ vstream_control(VSTREAM_IN, VSTREAM_CTL_PATH, "stdin", VSTREAM_CTL_END); buf = vstring_alloc(100); @@ -273,8 +322,12 @@ int main(int argc, char **argv) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); if (rec_type == **expected) expected++; - if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) - msg_fatal("uid=%ld: queue file write error", (long) uid); + if (REC_PUT_BUF(dst->stream, rec_type, buf) < 0) { + while ((rec_type = rec_get(VSTREAM_IN, buf, var_line_limit)) > 0 + && rec_type != REC_TYPE_END) + /* void */ ; + break; + } if (rec_type == REC_TYPE_END) break; } @@ -283,8 +336,10 @@ int main(int argc, char **argv) /* * Finish the file. */ - if ((status = mail_stream_finish(dst)) != 0) - msg_fatal("uid=%ld: %s", (long) uid, cleanup_strerror(status)); + if ((status = mail_stream_finish(dst, (VSTRING *) 0)) != 0) { + postdrop_cleanup(); + msg_warn("uid=%ld: %m", (long) uid); + } /* * Disable deletion on fatal error before reporting success, so the file @@ -298,7 +353,10 @@ int main(int argc, char **argv) /* * Send the completion status to the caller and terminate. */ - mail_print(VSTREAM_OUT, "%d", status); + attr_print(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, status, + ATTR_TYPE_STR, MAIL_ATTR_WHY, "", + ATTR_TYPE_END); vstream_fflush(VSTREAM_OUT); exit(status); } diff --git a/gnu/dist/postfix/src/postfix/Makefile.in b/gnu/dist/postfix/src/postfix/Makefile.in index 3b651dbecc3..45e05f7f978 100644 --- a/gnu/dist/postfix/src/postfix/Makefile.in +++ b/gnu/dist/postfix/src/postfix/Makefile.in @@ -66,5 +66,8 @@ postfix.o: ../../include/msg_vstream.h postfix.o: ../../include/msg_syslog.h postfix.o: ../../include/stringops.h postfix.o: ../../include/vstring.h +postfix.o: ../../include/clean_env.h +postfix.o: ../../include/argv.h +postfix.o: ../../include/safe.h postfix.o: ../../include/mail_conf.h postfix.o: ../../include/mail_params.h diff --git a/gnu/dist/postfix/src/postfix/postfix.c b/gnu/dist/postfix/src/postfix/postfix.c index 466118504bb..2db1dc24fb1 100644 --- a/gnu/dist/postfix/src/postfix/postfix.c +++ b/gnu/dist/postfix/src/postfix/postfix.c @@ -8,11 +8,15 @@ /* \fBpostfix\fR [\fB-c \fIconfig_dir\fR] [\fB-D\fR] [\fB-v\fR] /* \fIcommand\fR /* DESCRIPTION +/* This command is reserved for the super-user. To submit mail, +/* use the Postfix \fBsendmail\fR command. +/* /* The \fBpostfix\fR command controls the operation of the Postfix /* mail system: start or stop the \fBmaster\fR daemon, do a health -/* check, and other maintenance. The command sets up a standardized -/* environment and runs the \fBpostfix-script\fR shell script to -/* do the actual work. +/* check, and other maintenance. +/* +/* The \fBpostfix\fR command sets up a standardized environment and +/* runs the \fBpostfix-script\fR shell script to do the actual work. /* /* The following commands are implemented: /* .IP \fBcheck\fR @@ -63,25 +67,48 @@ /* This is set when the -v command-line option is present. /* .IP \fBMAIL_DEBUG\fR /* This is set when the -D command-line option is present. -/* .PP -/* The following configuration parameters are made available -/* as process environment variables with the same names: +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR configuration parameters are made +/* available as process environment variables with the same names: /* .IP \fBcommand_directory\fR -/* Directory with Postfix support commands (default: -/* \fB$program_directory\fR). +/* Directory with Postfix administrative commands. /* .IP \fBdaemon_directory\fR -/* Directory with Postfix daemon programs (default: -/* \fB$program_directory\fR). +/* Directory with Postfix daemon programs. /* .IP \fBconfig_directory\fR /* Directory with Postfix configuration files and with administrative /* shell scripts. /* .IP \fBqueue_directory\fR -/* The directory with the Postfix queue directory (and with some -/* files needed for programs running in a chrooted environment). +/* The directory with Postfix queue files, with local inter-process +/* communication endpoints, and with files needed for daemon programs +/* that run in the optional chrooted environment. /* .IP \fBmail_owner\fR -/* The owner of the Postfix queue and of most Postfix processes. +/* The owner of Postfix queue files and of most Postfix processes. +/* .IP \fBsetgid_group\fR +/* The group for mail submission and queue management commands. +/* .IP \fBsendmail_path +/* The full pathname for the Postfix sendmail command. +/* .IP \fBnewaliases_path +/* The full pathname for the Postfix newaliases command. +/* .IP \fBmailq_path +/* The full pathname for the Postfix mailq command. +/* .IP \fBmanpage_directory +/* The directory for the Postfix on-line manual pages. +/* .IP \fBsample_directory +/* The directory for the Postfix sample configuration files. +/* .IP \fBreadme_directory +/* The directory for the Postfix README files. +/* .SH Other configuration parameters +/* .ad +/* .fi +/* .IP \fBimport_environment\fR +/* List of names of environment parameters that can be imported +/* from non-Postfix processes. /* FILES /* $\fBconfig_directory/postfix-script\fR, administrative commands +/* $\fBconfig_directory/main.cf\fR, configuration parameters +/* $\fBconfig_directory/master.cf\fR, Postfix daemon processes /* SEE ALSO /* master(8) Postfix master program /* LICENSE @@ -115,12 +142,24 @@ #include <msg_vstream.h> #include <msg_syslog.h> #include <stringops.h> +#include <clean_env.h> +#include <argv.h> +#include <safe.h> /* Global library. */ #include <mail_conf.h> #include <mail_params.h> +/* Additional installation parameters. */ + +static char *var_sendmail_path; +static char *var_mailq_path; +static char *var_newalias_path; +static char *var_manpage_dir; +static char *var_sample_dir; +static char *var_readme_dir; + /* check_setenv - setenv() with extreme prejudice */ static void check_setenv(char *name, char *value) @@ -137,9 +176,18 @@ int main(int argc, char **argv) char *script; struct stat st; char *slash; - int uid; int fd; int ch; + ARGV *import_env; + static CONFIG_STR_TABLE str_table[] = { + VAR_SENDMAIL_PATH, DEF_SENDMAIL_PATH, &var_sendmail_path, 1, 0, + VAR_MAILQ_PATH, DEF_MAILQ_PATH, &var_mailq_path, 1, 0, + VAR_NEWALIAS_PATH, DEF_NEWALIAS_PATH, &var_newalias_path, 1, 0, + VAR_MANPAGE_DIR, DEF_MANPAGE_DIR, &var_manpage_dir, 1, 0, + VAR_SAMPLE_DIR, DEF_SAMPLE_DIR, &var_sample_dir, 1, 0, + VAR_README_DIR, DEF_README_DIR, &var_readme_dir, 1, 0, + 0, + }; /* * Be consistent with file permissions. @@ -172,10 +220,12 @@ int main(int argc, char **argv) * privileges for selected operations. That's right - it takes privileges * to toss privileges. */ - if ((uid = getuid()) != 0) { + if (getuid() != 0) { msg_error("to submit mail, use the Postfix sendmail command"); msg_fatal("the postfix command is reserved for the superuser"); } + if (unsafe() != 0) + msg_fatal("the postfix command must not run as a set-uid process"); /* * Parse switches. @@ -206,6 +256,17 @@ int main(int argc, char **argv) * scripts. */ mail_conf_read(); + get_mail_conf_str_table(str_table); + + /* + * Environment import filter, to enforce consistent behavior whether this + * command is started by hand, or at system boot time. This is necessary + * because some shell scripts use environment settings to override + * main.cf settings. + */ + import_env = argv_split(var_import_environ, ", \t\r\n"); + clean_env(import_env->argv); + argv_free(import_env); check_setenv("PATH", ROOT_PATH); /* sys_defs.h */ check_setenv(CONF_ENV_PATH, var_config_dir);/* mail_conf.h */ @@ -214,7 +275,18 @@ int main(int argc, char **argv) check_setenv(VAR_DAEMON_DIR, var_daemon_dir); /* main.cf */ check_setenv(VAR_QUEUE_DIR, var_queue_dir); /* main.cf */ check_setenv(VAR_CONFIG_DIR, var_config_dir); /* main.cf */ + + /* + * Do we want to keep adding things here as shell scripts evolve? + */ check_setenv(VAR_MAIL_OWNER, var_mail_owner); /* main.cf */ + check_setenv(VAR_SGID_GROUP, var_sgid_group); /* main.cf */ + check_setenv(VAR_SENDMAIL_PATH, var_sendmail_path); /* main.cf */ + check_setenv(VAR_MAILQ_PATH, var_mailq_path); /* main.cf */ + check_setenv(VAR_NEWALIAS_PATH, var_newalias_path); /* main.cf */ + check_setenv(VAR_MANPAGE_DIR, var_manpage_dir); /* main.cf */ + check_setenv(VAR_SAMPLE_DIR, var_sample_dir); /* main.cf */ + check_setenv(VAR_README_DIR, var_readme_dir); /* main.cf */ /* * Make sure these directories exist. Run the maintenance scripts with as diff --git a/gnu/dist/postfix/src/postkick/Makefile.in b/gnu/dist/postfix/src/postkick/Makefile.in index b34c9673614..d4cf100f460 100644 --- a/gnu/dist/postfix/src/postkick/Makefile.in +++ b/gnu/dist/postfix/src/postkick/Makefile.in @@ -61,7 +61,9 @@ postkick.o: ../../include/vstream.h postkick.o: ../../include/vbuf.h postkick.o: ../../include/msg_vstream.h postkick.o: ../../include/safe.h +postkick.o: ../../include/events.h postkick.o: ../../include/mail_proto.h postkick.o: ../../include/iostuff.h +postkick.o: ../../include/attr.h postkick.o: ../../include/mail_params.h postkick.o: ../../include/mail_conf.h diff --git a/gnu/dist/postfix/src/postkick/postkick.c b/gnu/dist/postfix/src/postkick/postkick.c index c756c14ae49..a4066a96aca 100644 --- a/gnu/dist/postfix/src/postkick/postkick.c +++ b/gnu/dist/postfix/src/postkick/postkick.c @@ -80,6 +80,7 @@ #include <vstream.h> #include <msg_vstream.h> #include <safe.h> +#include <events.h> /* Global library. */ @@ -164,6 +165,7 @@ int main(int argc, char **argv) class, service); exit(1); } else { + event_drain(); exit(0); } } diff --git a/gnu/dist/postfix/src/postmap/Makefile.in b/gnu/dist/postfix/src/postmap/Makefile.in index b2d64c2bf1d..a96270e30f8 100644 --- a/gnu/dist/postfix/src/postmap/Makefile.in +++ b/gnu/dist/postfix/src/postmap/Makefile.in @@ -21,13 +21,39 @@ $(PROG): $(OBJS) $(LIBS) Makefile: Makefile.in (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ -test: $(TESTPROG) - update: ../../bin/$(PROG) ../../bin/$(PROG): $(PROG) cp $(PROG) ../../bin +test: test1 test2 + +test1: $(PROG) map.in map-abc1.ref map-ghi1.ref map-ABC1.ref + ./$(PROG) map.in + for key in abc ghi; \ + do \ + ./$(PROG) -q $${key} map.in | diff map-$${key}1.ref -; \ + done + ./$(PROG) -f map.in + for key in ABC; \ + do \ + ./$(PROG) -fq $${key} map.in | diff map-$${key}1.ref -; \ + done + rm -f map.in.db + +test2: $(PROG) map.in map-abc2.ref map-ghi2.ref map-ABC2.ref + ./$(PROG) map.in + for key in abc ghi; \ + do \ + echo $${key} | ./$(PROG) -q - map.in | diff map-$${key}2.ref -; \ + done + ./$(PROG) -f map.in + for key in ABC; \ + do \ + echo $${key} | ./$(PROG) -fq - map.in | diff map-$${key}2.ref -; \ + done + rm -f map.in.db + printfck: $(OBJS) $(PROG) rm -rf printfck mkdir printfck @@ -39,7 +65,7 @@ lint: lint $(DEFS) $(SRCS) $(LINTFIX) clean: - rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -f *.o *core $(PROG) $(TESTPROG) junk map.in.db rm -rf printfck tidy: clean @@ -64,6 +90,7 @@ postmap.o: ../../include/msg_vstream.h postmap.o: ../../include/readlline.h postmap.o: ../../include/stringops.h postmap.o: ../../include/split_at.h +postmap.o: ../../include/vstring_vstream.h postmap.o: ../../include/mail_conf.h postmap.o: ../../include/mail_params.h postmap.o: ../../include/mkmap.h diff --git a/gnu/dist/postfix/src/postmap/map-ABC1.ref b/gnu/dist/postfix/src/postmap/map-ABC1.ref new file mode 100644 index 00000000000..142195f36ae --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-ABC1.ref @@ -0,0 +1 @@ +DEF diff --git a/gnu/dist/postfix/src/postmap/map-ABC2.ref b/gnu/dist/postfix/src/postmap/map-ABC2.ref new file mode 100644 index 00000000000..cc3ea4e3c40 --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-ABC2.ref @@ -0,0 +1 @@ +ABC DEF diff --git a/gnu/dist/postfix/src/postmap/map-abc1.ref b/gnu/dist/postfix/src/postmap/map-abc1.ref new file mode 100644 index 00000000000..142195f36ae --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-abc1.ref @@ -0,0 +1 @@ +DEF diff --git a/gnu/dist/postfix/src/postmap/map-abc2.ref b/gnu/dist/postfix/src/postmap/map-abc2.ref new file mode 100644 index 00000000000..2ee5fab43d9 --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-abc2.ref @@ -0,0 +1 @@ +abc DEF diff --git a/gnu/dist/postfix/src/postmap/map-ghi1.ref b/gnu/dist/postfix/src/postmap/map-ghi1.ref new file mode 100644 index 00000000000..7beb1db20dc --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-ghi1.ref @@ -0,0 +1 @@ +jkl diff --git a/gnu/dist/postfix/src/postmap/map-ghi2.ref b/gnu/dist/postfix/src/postmap/map-ghi2.ref new file mode 100644 index 00000000000..5926a187ff9 --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map-ghi2.ref @@ -0,0 +1 @@ +ghi jkl diff --git a/gnu/dist/postfix/src/postmap/map.in b/gnu/dist/postfix/src/postmap/map.in new file mode 100644 index 00000000000..f1805549ec2 --- /dev/null +++ b/gnu/dist/postfix/src/postmap/map.in @@ -0,0 +1,2 @@ +ABC DEF +ghi jkl diff --git a/gnu/dist/postfix/src/postmap/postmap.c b/gnu/dist/postfix/src/postmap/postmap.c index f82edfb1443..40579938e7a 100644 --- a/gnu/dist/postfix/src/postmap/postmap.c +++ b/gnu/dist/postfix/src/postmap/postmap.c @@ -5,8 +5,9 @@ /* Postfix lookup table management /* SYNOPSIS /* .fi -/* \fBpostmap\fR [\fB-Ninrvw\fR] [\fB-c \fIconfig_dir\fR] [\fB-d \fIkey\fR] -/* [\fB-q \fIkey\fR] [\fIfile_type\fR:]\fIfile_name\fR ... +/* \fBpostmap\fR [\fB-Nfinrvw\fR] [\fB-c \fIconfig_dir\fR] +/* [\fB-d \fIkey\fR] [\fB-q \fIkey\fR] +/* [\fIfile_type\fR:]\fIfile_name\fR ... /* DESCRIPTION /* The \fBpostmap\fR command creates or queries one or more Postfix /* lookup tables, or updates an existing one. The input and output @@ -15,6 +16,9 @@ /* .ti +4 /* \fBmakemap \fIfile_type\fR \fIfile_name\fR < \fIfile_name\fR /* +/* If the result files do not exist they will be created with the +/* same group and other read permissions as the source file. +/* /* While the table update is in progress, signal delivery is /* postponed, and an exclusive, advisory, lock is placed on the /* entire table, in order to avoid surprises in spectator @@ -22,14 +26,16 @@ /* /* The format of a lookup table input file is as follows: /* .IP \(bu -/* Blank lines are ignored. So are lines beginning with `#'. -/* .IP \(bu /* A table entry has the form /* .sp /* .ti +5 /* \fIkey\fR whitespace \fIvalue\fR /* .IP \(bu -/* A line that starts with whitespace continues the preceding line. +/* Empty lines and whitespace-only lines are ignored, as +/* are lines whose first non-whitespace character is a `#'. +/* .IP \(bu +/* A logical line starts with non-whitespace text. A line that +/* starts with whitespace continues a logical line. /* .PP /* The \fIkey\fR and \fIvalue\fR are processed as is, except that /* surrounding white space is stripped off. Unlike with Postfix alias @@ -47,7 +53,14 @@ /* instead of the default configuration directory. /* .IP "\fB-d \fIkey\fR" /* Search the specified maps for \fIkey\fR and remove one entry per map. -/* The exit status is non-zero if the requested information was not found. +/* The exit status is zero when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream. The exit status is zero +/* when at least one of the requested keys was found. +/* .IP \fB-f\fR +/* Do not fold the lookup key to lower case while creating or querying +/* a map. /* .IP \fB-i\fR /* Incremental mode. Read entries from standard input and do not /* truncate an existing database. By default, \fBpostmap\fR creates @@ -58,8 +71,13 @@ /* the host operating system. /* .IP "\fB-q \fIkey\fR" /* Search the specified maps for \fIkey\fR and print the first value -/* found on the standard output stream. The exit status is non-zero -/* if the requested information was not found. +/* found on the standard output stream. The exit status is zero +/* when the requested information was found. +/* +/* If a key value of \fB-\fR is specified, the program reads key +/* values from the standard input stream and prints one line of +/* \fIkey value\fR output for each key that was found. The exit +/* status is zero when at least one of the requested keys was found. /* .IP \fB-r\fR /* When updating a table, do not warn about duplicate entries; silently /* replace them. @@ -98,9 +116,6 @@ /* \fBpostmap\fR terminates with zero exit status in case of success /* (including successful \fBpostmap -q\fR lookup) and terminates /* with non-zero exit status in case of failure. -/* BUGS -/* The "delete key" support is limited to one delete operation -/* per command invocation. /* ENVIRONMENT /* .ad /* .fi @@ -146,6 +161,7 @@ #include <readlline.h> #include <stringops.h> #include <split_at.h> +#include <vstring_vstream.h> /* Global library. */ @@ -168,6 +184,8 @@ static void postmap(char *map_type, char *path_name, int lineno; char *key; char *value; + struct stat st; + mode_t saved_mask; /* * Initialize. @@ -179,6 +197,14 @@ static void postmap(char *map_type, char *path_name, } else if ((source_fp = vstream_fopen(path_name, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", path_name); } + if (fstat(vstream_fileno(source_fp), &st) < 0) + msg_fatal("fstat %s: %m", path_name); + + /* + * Turn off group/other read permissions as indicated in the source file. + */ + if (S_ISREG(st.st_mode)) + saved_mask = umask(022 | (~st.st_mode & 077)); /* * Open the database, optionally create it when it does not exist, @@ -188,39 +214,31 @@ static void postmap(char *map_type, char *path_name, mkmap = mkmap_open(map_type, path_name, open_flags, dict_flags); /* + * And restore the umask, in case it matters. + */ + if (S_ISREG(st.st_mode)) + umask(saved_mask); + + /* * Add records to the database. */ lineno = 0; - while (readlline(line_buffer, source_fp, &lineno, READLL_STRIPNL)) { - - /* - * Skip comments. - */ - if (*STR(line_buffer) == '#') - continue; + while (readlline(line_buffer, source_fp, &lineno)) { /* * Split on the first whitespace character, then trim leading and * trailing whitespace from key and value. */ key = STR(line_buffer); - value = STR(line_buffer) + strcspn(STR(line_buffer), " \t\r\n"); + value = key + strcspn(key, " \t\r\n"); if (*value) *value++ = 0; - while (ISSPACE(*key)) - key++; while (ISSPACE(*value)) value++; trimblanks(key, 0)[0] = 0; trimblanks(value, 0)[0] = 0; /* - * Skip empty lines, or lines with whitespace characters only. - */ - if (*key == 0 && *value == 0) - continue; - - /* * Enforce the "key whitespace value" format. Disallow missing keys * or missing values. */ @@ -236,7 +254,8 @@ static void postmap(char *map_type, char *path_name, /* * Store the value under a case-insensitive key. */ - lowercase(key); + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(key); mkmap_append(mkmap, key, value); } @@ -253,6 +272,65 @@ static void postmap(char *map_type, char *path_name, vstream_fclose(source_fp); } +/* postmap_queries - apply multiple requests from stdin */ + +static int postmap_queries(VSTREAM *in, char **maps, const int map_count, + const int dict_flags) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + const char *value; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postmap_queries: bad map count"); + + /* + * Prepare to open maps lazily. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = 0; + + /* + * Perform all queries. Open maps on the fly, to avoid opening unecessary + * maps. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) { + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(STR(keybuf)); + for (n = 0; n < map_count; n++) { + if (dicts[n] == 0) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDONLY, DICT_FLAG_LOCK) : + dict_open3(var_db_type, maps[n], O_RDONLY, DICT_FLAG_LOCK)); + if ((value = dict_get(dicts[n], STR(keybuf))) != 0) { + vstream_printf("%s %s\n", STR(keybuf), value); + found = 1; + break; + } + } + } + if (found) + vstream_fflush(VSTREAM_OUT); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((char *) dicts); + vstring_free(keybuf); + + return (found); +} + /* postmap_query - query a map and print the result to stdout */ static int postmap_query(const char *map_type, const char *map_name, @@ -270,6 +348,50 @@ static int postmap_query(const char *map_type, const char *map_name, return (value != 0); } +/* postmap_deletes - apply multiple requests from stdin */ + +static int postmap_deletes(VSTREAM *in, char **maps, const int map_count) +{ + int found = 0; + VSTRING *keybuf = vstring_alloc(100); + DICT **dicts; + const char *map_name; + int n; + + /* + * Sanity check. + */ + if (map_count <= 0) + msg_panic("postmap_deletes: bad map count"); + + /* + * Open maps ahead of time. + */ + dicts = (DICT **) mymalloc(sizeof(*dicts) * map_count); + for (n = 0; n < map_count; n++) + dicts[n] = ((map_name = split_at(maps[n], ':')) != 0 ? + dict_open3(maps[n], map_name, O_RDWR, DICT_FLAG_LOCK) : + dict_open3(var_db_type, maps[n], O_RDWR, DICT_FLAG_LOCK)); + + /* + * Perform all requests. + */ + while (vstring_get_nonl(keybuf, in) != VSTREAM_EOF) + for (n = 0; n < map_count; n++) + found |= (dict_del(dicts[n], STR(keybuf)) == 0); + + /* + * Cleanup. + */ + for (n = 0; n < map_count; n++) + if (dicts[n]) + dict_close(dicts[n]); + myfree((char *) dicts); + vstring_free(keybuf); + + return (found); +} + /* postmap_delete - delete a (key, value) pair from a map */ static int postmap_delete(const char *map_type, const char *map_name, @@ -278,10 +400,6 @@ static int postmap_delete(const char *map_type, const char *map_name, DICT *dict; int status; - /* - * XXX This must be generalized to multi-key (read from stdin) and - * multi-map (given on command line) updates. - */ dict = dict_open3(map_type, map_name, O_RDWR, DICT_FLAG_LOCK); status = dict_del(dict, key); dict_close(dict); @@ -292,7 +410,7 @@ static int postmap_delete(const char *map_type, const char *map_name, static NORETURN usage(char *myname) { - msg_fatal("usage: %s [-Ninrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", + msg_fatal("usage: %s [-Nfinrvw] [-c config_dir] [-d key] [-q key] [map_type:]file...", myname); } @@ -304,7 +422,7 @@ int main(int argc, char **argv) char *slash; struct stat st; int open_flags = O_RDWR | O_CREAT | O_TRUNC; - int dict_flags = DICT_FLAG_DUP_WARN; + int dict_flags = DICT_FLAG_DUP_WARN | DICT_FLAG_FOLD_KEY; char *query = 0; char *delkey = 0; int found; @@ -342,7 +460,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "Nc:d:inq:rvw")) > 0) { + while ((ch = GETOPT(argc, argv, "Nc:d:finq:rvw")) > 0) { switch (ch) { default: usage(argv[0]); @@ -360,6 +478,9 @@ int main(int argc, char **argv) msg_fatal("specify only one of -q or -d"); delkey = optarg; break; + case 'f': + dict_flags &= ~DICT_FLAG_FOLD_KEY; + break; case 'i': open_flags &= ~O_TRUNC; break; @@ -394,6 +515,8 @@ int main(int argc, char **argv) if (delkey) { /* remove entry */ if (optind + 1 > argc) usage(argv[0]); + if (strcmp(delkey, "-") == 0) + exit(postmap_deletes(VSTREAM_IN, argv + optind, argc - optind) == 0); found = 0; while (optind < argc) { if ((path_name = split_at(argv[optind], ':')) != 0) { @@ -407,6 +530,11 @@ int main(int argc, char **argv) } else if (query) { /* query map(s) */ if (optind + 1 > argc) usage(argv[0]); + if (strcmp(query, "-") == 0) + exit(postmap_queries(VSTREAM_IN, argv + optind, argc - optind, + dict_flags) == 0); + if (dict_flags & DICT_FLAG_FOLD_KEY) + lowercase(query); while (optind < argc) { if ((path_name = split_at(argv[optind], ':')) != 0) { found = postmap_query(argv[optind], path_name, query); diff --git a/gnu/dist/postfix/src/postqueue/Makefile.in b/gnu/dist/postfix/src/postqueue/Makefile.in new file mode 100644 index 00000000000..f9470cc9a47 --- /dev/null +++ b/gnu/dist/postfix/src/postqueue/Makefile.in @@ -0,0 +1,80 @@ +SHELL = /bin/sh +SRCS = postqueue.c +OBJS = postqueue.o +HDRS = +TESTSRC = +WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ + -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ + -Wunused +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = postqueue +INC_DIR = ../../include +LIBS = ../../lib/libglobal.a ../../lib/libutil.a + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +Makefile: Makefile.in + (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ + +test: $(TESTPROG) + +update: ../../bin/$(PROG) + +../../bin/$(PROG): $(PROG) + cp $(PROG) ../../bin + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \ + done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +postqueue.o: postqueue.c +postqueue.o: ../../include/sys_defs.h +postqueue.o: ../../include/msg.h +postqueue.o: ../../include/mymalloc.h +postqueue.o: ../../include/clean_env.h +postqueue.o: ../../include/vstream.h +postqueue.o: ../../include/vbuf.h +postqueue.o: ../../include/msg_vstream.h +postqueue.o: ../../include/msg_syslog.h +postqueue.o: ../../include/argv.h +postqueue.o: ../../include/safe.h +postqueue.o: ../../include/connect.h +postqueue.o: ../../include/iostuff.h +postqueue.o: ../../include/valid_hostname.h +postqueue.o: ../../include/mail_proto.h +postqueue.o: ../../include/attr.h +postqueue.o: ../../include/mail_params.h +postqueue.o: ../../include/mail_conf.h +postqueue.o: ../../include/mail_task.h +postqueue.o: ../../include/debug_process.h +postqueue.o: ../../include/mail_run.h +postqueue.o: ../../include/mail_flush.h +postqueue.o: ../../include/flush_clnt.h +postqueue.o: ../../include/smtp_stream.h +postqueue.o: ../../include/vstring.h diff --git a/gnu/dist/postfix/src/postqueue/postqueue.c b/gnu/dist/postfix/src/postqueue/postqueue.c new file mode 100644 index 00000000000..7fba3c61d5b --- /dev/null +++ b/gnu/dist/postfix/src/postqueue/postqueue.c @@ -0,0 +1,396 @@ +/*++ +/* NAME +/* postqueue 1 +/* SUMMARY +/* Postfix queue control +/* SYNOPSIS +/* \fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-f\fR +/* .br +/* \fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-p\fR +/* .br +/* \fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-s \fIsite\fR +/* DESCRIPTION +/* The \fBpostqueue\fR program implements the Postfix user interface +/* for queue management. It implements all the operations that are +/* traditionally available via the \fBsendmail\fR(1) command. +/* +/* The following options are recognized: +/* .IP "\fB-c \fIconfig_dir\fR" +/* The \fBmain.cf\fR configuration file is in the named directory +/* instead of the default configuration directory. See also the +/* MAIL_CONFIG environment setting below. +/* .IP \fB-f\fR +/* Flush the queue: attempt to deliver all queued mail. +/* +/* This option implements the traditional \fBsendmail -q\fR command, +/* by contacting the Postfix \fBqmgr\fR(8) daemon. +/* .IP \fB-p\fR +/* Produce a traditional sendmail-style queue listing. +/* +/* This option implements the traditional \fBmailq\fR command, +/* by contacting the Postfix \fBshowq\fR(8) daemon. +/* .IP "\fB-s \fIsite\fR" +/* Schedule immediate delivery of all mail that is queued for the named +/* \fIsite\fR. The site must be eligible for the "fast flush" service. +/* See \fBflush\fR(8) for more information about the "fast flush" +/* service. +/* +/* This option implements the traditional \fBsendmail -qR\fIsite\fR +/* command, by contacting the Postfix \fBflush\fR(8) daemon. +/* .IP \fB-v\fR +/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR +/* options make the software increasingly verbose. +/* SECURITY +/* .ad +/* .fi +/* This program is designed to run with set-group ID privileges, so +/* that it can connect to Postfix daemon processes. +/* DIAGNOSTICS +/* Problems are logged to \fBsyslogd\fR(8) and to the standard error +/* stream. +/* ENVIRONMENT +/* .ad +/* .fi +/* .IP MAIL_CONFIG +/* Directory with the \fBmain.cf\fR file. +/* +/* In order to avoid exploitation of set-group ID privileges, it is not +/* possible to specify arbitrary directory names. +/* +/* A non-standard directory is allowed only if the name is listed in the +/* standard \fBmain.cf\fR file, in the \fBalternate_config_directories\fR +/* configuration parameter value. +/* +/* Only the super-user is allowed to specify arbitrary directory names. +/* FILES +/* /var/spool/postfix, mail queue +/* /etc/postfix, configuration files +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* .IP \fBimport_environment\fR +/* List of names of environment parameters that can be imported +/* from non-Postfix processes. +/* .IP \fBqueue_directory\fR +/* Top-level directory of the Postfix queue. This is also the root +/* directory of Postfix daemons that run chrooted. +/* .IP \fBfast_flush_domains\fR +/* List of domains that will receive "fast flush" service (default: all +/* domains that this system is willing to relay mail to). This list +/* specifies the domains that Postfix accepts in the SMTP \fBETRN\fR +/* request and in the \fBsendmail -qR\fR command. +/* SEE ALSO +/* sendmail(8) sendmail-compatible user interface +/* qmgr(8) queue manager +/* showq(8) list mail queue +/* flush(8) fast flush service +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <sysexits.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <clean_env.h> +#include <vstream.h> +#include <msg_vstream.h> +#include <msg_syslog.h> +#include <argv.h> +#include <safe.h> +#include <connect.h> +#include <valid_hostname.h> + +/* Global library. */ + +#include <mail_proto.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_task.h> +#include <debug_process.h> +#include <mail_run.h> +#include <mail_flush.h> +#include <flush_clnt.h> +#include <smtp_stream.h> + +/* Application-specific. */ + + /* + * WARNING WARNING WARNING + * + * This software is designed to run set-gid. In order to avoid exploitation of + * privilege, this software should not run any external commands, nor should + * it take any information from the user, unless that information can be + * properly sanitized. To get an idea of how much information a process can + * inherit from a potentially hostile user, examine all the members of the + * process structure (typically, in /usr/include/sys/proc.h): the current + * directory, open files, timers, signals, environment, command line, umask, + * and so on. + */ + + /* + * Modes of operation. + */ +#define PQ_MODE_DEFAULT 0 /* noop */ +#define PQ_MODE_MAILQ_LIST 1 /* list mail queue */ +#define PQ_MODE_FLUSH_QUEUE 2 /* flush queue */ +#define PQ_MODE_FLUSH_SITE 3 /* flush site */ + + /* + * Silly little macros (SLMs). + */ +#define STR vstring_str + +/* show_queue - show queue status */ + +static void show_queue(void) +{ + char buf[VSTREAM_BUFSIZE]; + VSTREAM *showq; + int n; + + /* + * Connect to the show queue service. Terminate silently when piping into + * a program that terminates early. + */ + if ((showq = mail_connect(MAIL_CLASS_PUBLIC, MAIL_SERVICE_SHOWQ, BLOCKING)) != 0) { + while ((n = vstream_fread(showq, buf, sizeof(buf))) > 0) + if (vstream_fwrite(VSTREAM_OUT, buf, n) != n + || vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + if (vstream_fclose(showq)) + msg_warn("close: %m"); + } + + /* + * When the mail system is down, the superuser can still access the queue + * directly. Just run the showq program in stand-alone mode. + */ + else if (geteuid() == 0) { + ARGV *argv; + int stat; + + msg_warn("Mail system is down -- accessing queue directly"); + argv = argv_alloc(6); + argv_add(argv, MAIL_SERVICE_SHOWQ, "-u", "-S", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(argv, "-v", (char *) 0); + argv_terminate(argv); + stat = mail_run_foreground(var_daemon_dir, argv->argv); + argv_free(argv); + } + + /* + * When the mail system is down, unprivileged users are stuck, because by + * design the mail system contains no set_uid programs. The only way for + * an unprivileged user to cross protection boundaries is to talk to the + * showq daemon. + */ + else { + msg_fatal_status(EX_UNAVAILABLE, + "Queue report unavailable - mail system is down"); + } +} + +/* flush_queue - force delivery */ + +static void flush_queue(void) +{ + + /* + * Trigger the flush queue service. + */ + if (mail_flush_deferred() < 0) + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); +} + +/* flush_site - flush mail for site */ + +static void flush_site(const char *site) +{ + int status; + + switch (status = flush_send(site)) { + case FLUSH_STAT_OK: + exit(0); + case FLUSH_STAT_BAD: + msg_fatal_status(EX_USAGE, "Invalid request: \"%s\"", site); + case FLUSH_STAT_FAIL: + msg_fatal_status(EX_UNAVAILABLE, + "Cannot flush mail queue - mail system is down"); + case FLUSH_STAT_DENY: + msg_fatal_status(EX_UNAVAILABLE, + "Flush service is not configured for destination \"%s\"", + site); + default: + msg_fatal_status(EX_SOFTWARE, + "Unknown flush server reply status %d", status); + } +} + +/* usage - scream and die */ + +static NORETURN usage(void) +{ + msg_fatal_status(EX_USAGE, "usage: specify one of -f, -p, or -s"); +} + +/* main - the main program */ + +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; + ARGV *import_env; + char *last; + + /* + * 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) + argv[0] = slash + 1; + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_syslog_init(mail_task("postqueue"), LOG_PID, LOG_FACILITY); + set_mail_conf_str(VAR_PROCNAME, var_procname = mystrdup(argv[0])); + + /* + * 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:fps: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 'p': /* traditional mailq */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_MAILQ_LIST; + break; + break; + case 's': /* flush site */ + if (mode != PQ_MODE_DEFAULT) + usage(); + mode = PQ_MODE_FLUSH_SITE; + site_to_flush = optarg; + break; + case 'v': + msg_verbose++; + break; + default: + usage(); + } + } + + /* + * Further initialization... + */ + mail_conf_read(); + + /* + * Strip the environment so we don't have to trust the C library. + */ + import_env = argv_split(var_import_environ, ", \t\r\n"); + 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) { + if (*site_to_flush == '[' + && *(last = site_to_flush + strlen(site_to_flush) - 1) == ']') { + *last = 0; + if (!valid_hostaddr(site_to_flush + 1, DONT_GRIPE)) + site_to_flush = 0; + *last = ']'; + } else { + if (!valid_hostname(site_to_flush, DONT_GRIPE) + && !valid_hostaddr(site_to_flush, DONT_GRIPE)) + site_to_flush = 0; + } + if (site_to_flush == 0) + msg_fatal_status(EX_USAGE, + "Cannot flush mail queue - invalid destination: \"%.100s%s\"", + optarg, strlen(optarg) > 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_QUEUE: + flush_queue(); + exit(0); + break; + case PQ_MODE_DEFAULT: + usage(); + /* NOTREACHED */ + } +} diff --git a/gnu/dist/postfix/src/postsuper/Makefile.in b/gnu/dist/postfix/src/postsuper/Makefile.in index 3ff67a4d3df..4afa39a5434 100644 --- a/gnu/dist/postfix/src/postsuper/Makefile.in +++ b/gnu/dist/postfix/src/postsuper/Makefile.in @@ -66,7 +66,10 @@ postsuper.o: ../../include/vstring.h postsuper.o: ../../include/safe.h postsuper.o: ../../include/set_ugid.h postsuper.o: ../../include/argv.h +postsuper.o: ../../include/vstring_vstream.h +postsuper.o: ../../include/sane_fsops.h postsuper.o: ../../include/mail_task.h postsuper.o: ../../include/mail_conf.h postsuper.o: ../../include/mail_params.h postsuper.o: ../../include/mail_queue.h +postsuper.o: ../../include/mail_open_ok.h diff --git a/gnu/dist/postfix/src/postsuper/postsuper.c b/gnu/dist/postfix/src/postsuper/postsuper.c index b762d0849c7..660cf9ca718 100644 --- a/gnu/dist/postfix/src/postsuper/postsuper.c +++ b/gnu/dist/postfix/src/postsuper/postsuper.c @@ -2,37 +2,109 @@ /* NAME /* postsuper 1 /* SUMMARY -/* Postfix super intendent +/* Postfix superintendent /* SYNOPSIS /* .fi -/* \fBpostsuper\fR [\fB-p\fR] [\fB-s\fR] [\fB-v\fR] [\fIdirectory ...\fR] +/* \fBpostsuper\fR [\fB-psv\fR] [\fB-d \fIqueue_id\fR] +/* [\fB-r \fIqueue_id\fR] [\fIdirectory ...\fR] /* DESCRIPTION -/* The \fBpostsuper\fR command does small maintenance jobs on the named -/* Postfix queue directories (default: all). -/* Directory names are relative to the Postfix top-level queue directory. +/* The \fBpostsuper\fR command does maintenance jobs on the Postfix +/* queue. Use of the command is restricted to the super-user. /* /* By default, \fBpostsuper\fR performs the operations requested with the -/* \fB-s\fR and \fB-p\fR command-line options. -/* \fBpostsuper\fR always tries to remove objects that are neither files -/* nor directories. Use of this command is restricted to the super-user. +/* \fB-s\fR and \fB-p\fR command-line options on all Postfix queue +/* directories - this includes the \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR directories with mail files and the \fBbounce\fR, +/* \fBdefer\fR and \fBflush\fR directories with log files. /* /* Options: -/* .IP \fB-s\fR -/* Structure check. Move queue files that are in the wrong place -/* in the file system hierarchy and remove subdirectories that are -/* no longer needed. File rearrangements are necessary after a change -/* in the \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR -/* configuration parameters. It is highly recommended to run this -/* check once before Postfix startup. +/* .IP "\fB-d \fIqueue_id\fR" +/* Delete one message with the named queue ID from the named +/* mail queue(s) (default: \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR). +/* If a \fIqueue_id\fR of \fB-\fR is specified, the program reads +/* queue IDs from standard input. +/* .sp +/* Specify \fB-d ALL\fR to remove all messages; for example, specify +/* \fB-d ALL deferred\fR to delete mail in the \fBdeferred\fR queue. +/* As a safety measure, the word \fBALL\fR must be specified in upper +/* case. +/* .sp +/* .ft B +/* Postfix queue IDs are reused. +/* There is a very small possibility that postsuper deletes the +/* wrong message file when it is executed while the Postfix mail +/* system is running. +/* .ft R +/* .sp +/* The scenario is as follows: +/* .RS +/* .IP 1) +/* The Postfix queue manager deletes the message that \fBpostsuper\fR +/* is supposed to delete, because Postfix is finished with the +/* message. +/* .IP 2) +/* New mail arrives, and the new message is given the same queue ID +/* as the message that \fBpostsuper\fR is supposed to delete. +/* The probability for reusing a deleted queue ID is about 1 in 2**15 +/* (the number of different microsecond values that the system clock +/* can distinguish within a second). +/* .IP 3) +/* \fBpostsuper\fR deletes the new message, instead of the old +/* message that it should have deleted. +/* .RE /* .IP \fB-p\fR -/* Purge stale files (files that are left over after system or -/* software crashes). +/* Purge old temporary files that are left over after system or +/* software crashes. +/* .IP "\fB-r \fIqueue_id\fR" +/* Requeue the message with the named queue ID from the named +/* mail queue(s) (default: \fBincoming\fR, \fBactive\fR and +/* \fBdeferred\fR). +/* To requeue multiple messages, specify multiple \fB-r\fR +/* command-line options. +/* Alternatively, if a \fIqueue_id\fR of \fB-\fR is specified, +/* the program reads queue IDs from standard input. +/* .sp +/* Specify \fB-r ALL\fR to requeue all messages. As a safety +/* measure, the word \fBALL\fR must be specified in upper case. +/* .sp +/* A requeued message is moved to the \fBmaildrop\fR queue, from +/* where it is copied by the pickup daemon to a new file whose name +/* is guaranteed to match the new queue file inode number. The +/* new queue file is subjected again to mail address rewriting and +/* substitution. This is useful when rewriting rules or virtual +/* mappings have changed. +/* .sp +/* Postfix queue IDs are reused. +/* There is a very small possibility that \fBpostsuper\fR requeues +/* the wrong message file when it is executed while the Postfix mail +/* system is running, but no harm should be done. +/* .IP \fB-s\fR +/* Structure check and structure repair. It is highly recommended +/* to perform this operation once before Postfix startup. +/* .RS +/* .IP \(bu +/* Rename files whose name does not match the message file inode +/* number. This operation is necessary after restoring a mail queue +/* from a different machine, or from backup media. +/* .IP \(bu +/* Move queue files that are in the wrong place in the file system +/* hierarchy and remove subdirectories that are no longer needed. +/* File position rearrangements are necessary after a change in the +/* \fBhash_queue_names\fR and/or \fBhash_queue_depth\fR +/* configuration parameters. +/* .RE /* .IP \fB-v\fR /* Enable verbose logging for debugging purposes. Multiple \fB-v\fR /* options make the software increasingly verbose. /* DIAGNOSTICS /* Problems are reported to the standard error stream and to /* \fBsyslogd\fR. +/* +/* \fBpostsuper\fR reports the number of messages deleted with \fB-d\fR, +/* the number of messages requeued with \fB-r\fR, and the number of +/* messages whose queue file name was fixed with \fB-s\fR. The report +/* is written to the standard error stream and to \fBsyslogd\fR. /* CONFIGURATION PARAMETERS /* .ad /* .fi @@ -62,6 +134,7 @@ #include <stdlib.h> #include <errno.h> #include <string.h> +#include <signal.h> #include <stdio.h> /* remove() */ /* Utility library. */ @@ -76,6 +149,8 @@ #include <safe.h> #include <set_ugid.h> #include <argv.h> +#include <vstring_vstream.h> +#include <sane_fsops.h> /* Global library. */ @@ -83,6 +158,7 @@ #include <mail_conf.h> #include <mail_params.h> #include <mail_queue.h> +#include <mail_open_ok.h> /* Application-specific. */ @@ -91,6 +167,10 @@ #define ACTION_STRUCT (1<<0) /* fix file organization */ #define ACTION_PURGE (1<<1) /* purge old temp files */ +#define ACTION_DELETE_ONE (1<<2) /* delete named queue file(s) */ +#define ACTION_DELETE_ALL (1<<3) /* delete all queue file(s) */ +#define ACTION_REQUEUE_ONE (1<<4) /* requeue named queue file(s) */ +#define ACTION_REQUEUE_ALL (1<<5) /* requeue all queue file(s) */ #define ACTION_DEFAULT (ACTION_STRUCT | ACTION_PURGE) @@ -121,21 +201,276 @@ static struct queue_info queue_info[] = { 0, }; -/* super - check queue file location and clean up */ + /* + * Directories with per-message meta files. + */ +const char *log_queue_names[] = { + MAIL_QUEUE_BOUNCE, + MAIL_QUEUE_DEFER, + 0, +}; + + /* + * Cruft that we append to a file name when a queue ID is named after the + * message file inode number. This cruft must not pass mail_queue_id_ok() so + * that the queue manager will ignore it, should people be so unwise as to + * run this operation on a live mail system. + */ +#define SUFFIX "#FIX" +#define SUFFIX_LEN 4 + + /* + * Grr. These counters are global, because C only has clumsy ways to return + * multiple results from a function. + */ +static int message_requeued = 0; /* requeued messages */ +static int message_deleted = 0; /* deleted messages */ +static int inode_fixed = 0; /* queue id matched to inode number */ +static int inode_mismatch = 0; /* queue id inode mismatch */ +static int position_mismatch = 0; /* file position mismatch */ + + /* + * Silly little macros. These translate arcane expressions into something + * more at a conceptual level. + */ +#define MESSAGE_QUEUE(qp) ((qp)->perms == MAIL_QUEUE_STAT_READY) +#define READY_MESSAGE(st) (((st).st_mode & S_IRWXU) == MAIL_QUEUE_STAT_READY) + +/* find_queue_info - look up expected permissions field by queue name */ + +static struct queue_info *find_queue_info(const char *queue_name) +{ + struct queue_info *qp; + + for (qp = queue_info; qp->name; qp++) + if (strcmp(queue_name, qp->name) == 0) + return (qp); + msg_fatal("invalid directory name: %s", queue_name); +} + +/* postremove - remove file with extreme prejudice */ + +static int postremove(const char *path) +{ + int ret; + + if ((ret = remove(path)) < 0) { + if (errno != ENOENT) + msg_fatal("remove file %s: %m", path); + } else { + if (msg_verbose) + msg_info("removed file %s", path); + } + return (ret); +} + +/* postrename - rename file with extreme prejudice */ + +static int postrename(const char *old, const char *new) +{ + int ret; + + if ((ret = sane_rename(old, new)) < 0) { + if (errno != ENOENT + || mail_queue_mkdirs(new) < 0 + || sane_rename(old, new) < 0) + if (errno != ENOENT) + msg_fatal("rename file %s as %s: %m", old, new); + } else { + if (msg_verbose) + msg_info("renamed file %s as %s", old, new); + } + return (ret); +} + +/* postrmdir - remove directory with extreme prejudice */ + +static int postrmdir(const char *path) +{ + int ret; + + if ((ret = rmdir(path)) < 0) { + if (errno != ENOENT) + msg_fatal("remove directory %s: %m", path); + } else { + if (msg_verbose) + msg_info("remove directory %s", path); + } + return (ret); +} + +/* delete_one - delete one message instance and all its associated files */ + +static int delete_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char **log_qpp; + const char *msg_path; + VSTRING *log_path_buf; + int found; + int tries; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return (0); + } + log_path_buf = vstring_alloc(100); + + /* + * Skip meta file directories. Delete defer or bounce logfiles before + * deleting the corresponding message file, and only if the message file + * exists. This minimizes but does not eliminate a race condition with + * queue ID reuse which results in deleting the wrong files. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES) + continue; + for (log_qpp = log_queue_names; *log_qpp != 0; log_qpp++) + postremove(mail_queue_path(log_path_buf, *log_qpp, queue_id)); + if (postremove(msg_path) == 0) { + found = 1; + msg_info("%s: removed", queue_id); + break; + } /* else: maybe lost a race */ + } + } + vstring_free(log_path_buf); + return (found); +} + +/* requeue_one - requeue one message instance and delete its logfiles */ + +static int requeue_one(const char **queue_names, const char *queue_id) +{ + struct stat st; + const char **msg_qpp; + const char *old_path; + VSTRING *new_path_buf; + int found; + int tries; + + /* + * Sanity check. No early returns beyond this point. + */ + if (!mail_queue_id_ok(queue_id)) { + msg_warn("invalid mail queue id: %s", queue_id); + return (0); + } + new_path_buf = vstring_alloc(100); + + /* + * Skip meta file directories. Like the mass requeue operation, we not + * delete defer or bounce logfiles, to avoid losing a race where the + * queue manager decides to bounce mail after all recipients have been + * tried. + */ + for (found = 0, tries = 0; found == 0 && tries < 2; tries++) { + for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) { + if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0) + continue; + if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp))) + continue; + if (mail_open_ok(*msg_qpp, queue_id, &st, &old_path) != MAIL_OPEN_YES) + continue; + (void) mail_queue_path(new_path_buf, MAIL_QUEUE_MAILDROP, queue_id); + if (postrename(old_path, STR(new_path_buf)) == 0) { + msg_info("%s: requeued", queue_id); + found = 1; + break; + } /* else: maybe lost a race */ + } + } + vstring_free(new_path_buf); + return (found); +} + +/* operate_stream - operate on queue IDs given on stream */ + +static int operate_stream(VSTREAM *fp, + int (*operator) (const char **, const char *), + const char **queues) +{ + VSTRING *buf = vstring_alloc(20); + int found = 0; + + while (vstring_get_nonl(buf, fp) != VSTREAM_EOF) + found += operator(queues, STR(buf)); + + vstring_free(buf); + return (found); +} + +/* fix_queue_id - make message queue ID match inode number */ + +static int fix_queue_id(const char *actual_path, const char *actual_queue, + const char *actual_id, ino_t inum) +{ + VSTRING *old_path = vstring_alloc(10); + VSTRING *new_path = vstring_alloc(10); + VSTRING *new_id = vstring_alloc(10); + const char **log_qpp; + int ret; + + /* + * Create the new queue ID from the existing time digits and from the new + * inode number. Since we are renaming multiple files, the new name must + * be deterministic so that we can recover even when the renaming + * operation is interrupted in the middle. + */ + vstring_sprintf(new_id, "%.5s%lX", actual_id, (unsigned long) inum); + + /* + * Rename logfiles before renaming the message file, so that we can + * recover when a previous attempt was interrupted. + */ + for (log_qpp = log_queue_names; *log_qpp; log_qpp++) { + mail_queue_path(old_path, *log_qpp, actual_id); + mail_queue_path(new_path, *log_qpp, STR(new_id)); + vstring_strcat(new_path, SUFFIX); + postrename(STR(old_path), STR(new_path)); + } + + /* + * Rename the message file last, so that we know that we are done with + * this message and with all its logfiles. + */ + mail_queue_path(new_path, actual_queue, STR(new_id)); + vstring_strcat(new_path, SUFFIX); + ret = postrename(actual_path, STR(new_path)); + + /* + * Clean up. + */ + vstring_free(old_path); + vstring_free(new_path); + vstring_free(new_id); + + return (ret); +} + +/* super - check queue structure, clean up, do wild-card operations */ -static void super(char **queues, int action) +static void super(const char **queues, int action) { ARGV *hash_queue_names = argv_split(var_hash_queue_names, " \t\r\n,"); VSTRING *actual_path = vstring_alloc(10); VSTRING *wanted_path = vstring_alloc(10); struct stat st; - char *queue_name; + const char *queue_name; SCAN_DIR *info; char *path; int actual_depth; int wanted_depth; char **cpp; struct queue_info *qp; + unsigned long inum; /* * Make sure every file is in the right place, clean out stale files, and @@ -151,12 +486,7 @@ static void super(char **queues, int action) * file permissions to look for, and whether or not it is desirable * to step into subdirectories. */ - for (qp = queue_info; /* void */ ; qp++) { - if (qp->name == 0) - msg_fatal("unknown queue name: %s", queue_name); - if (strcmp(qp->name, queue_name) == 0) - break; - } + qp = find_queue_info(queue_name); for (cpp = hash_queue_names->argv; /* void */ ; cpp++) { if (*cpp == 0) { wanted_depth = 0; @@ -189,12 +519,8 @@ static void super(char **queues, int action) if ((path = scan_dir_next(info)) == 0) { if (actual_depth == 0) break; - if (actual_depth > wanted_depth) { - if (rmdir(scan_dir_path(info)) < 0 && errno != ENOENT) - msg_warn("remove %s: %m", scan_dir_path(info)); - else if (msg_verbose) - msg_info("remove %s", scan_dir_path(info)); - } + if (actual_depth > wanted_depth) + postrmdir(scan_dir_path(info)); scan_dir_pop(info); actual_depth--; continue; @@ -212,66 +538,175 @@ static void super(char **queues, int action) } /* - * See if this is a stale file or some non-file object. Be - * careful not to delete bounce or defer logs just because they - * are more than a couple days old. + * From here on we need to keep track of operations that + * invalidate or revalidate the actual_path and path variables, + * otherwise we can hit the wrong files. */ vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path); if (stat(STR(actual_path), &st) < 0) continue; + + /* + * Remove alien directories. If maildrop is world writable, then + * we cannot abort just because we cannot remove someone's + * directory. + */ if (S_ISDIR(st.st_mode)) { - if (rmdir(STR(actual_path)) < 0 && errno != ENOENT) - msg_warn("remove subdirectory %s: %m", STR(actual_path)); - else if (msg_verbose) - msg_info("remove subdirectory %s", STR(actual_path)); + if (rmdir(STR(actual_path)) < 0) { + if (errno != ENOENT) + msg_warn("remove subdirectory %s: %m", STR(actual_path)); + } else { + if (msg_verbose) + msg_info("remove subdirectory %s", STR(actual_path)); + } + /* No further work on this object is possible. */ + continue; + } + + /* + * Mass deletion. We count the deletion of mail that this system + * has taken responsibility for. XXX This option does not use the + * mail_queue(3) API, so that it can be run on a queue that does + * not have files in the "right" place. It would be terribly + * inefficient to first have to rename files into place before + * deleting them. + */ + if (action & ACTION_DELETE_ALL) { + if (postremove(STR(actual_path)) == 0) + if (MESSAGE_QUEUE(qp) && READY_MESSAGE(st)) + message_deleted++; + /* No further work on this object is possible. */ continue; } + + /* + * Remove non-file objects and old temporary files. Be careful + * not to delete bounce or defer logs just because they are more + * than a couple days old. + */ if (!S_ISREG(st.st_mode) - || ((action & ACTION_PURGE) != 0 && - (st.st_mode & S_IRWXU) != qp->perms + || ((action & ACTION_PURGE) != 0 + && MESSAGE_QUEUE(qp) + && !READY_MESSAGE(st) && time((time_t *) 0) > st.st_mtime + MAX_TEMP_AGE)) { - if (remove(STR(actual_path)) < 0 && errno != ENOENT) - msg_warn("remove %s: %m", STR(actual_path)); - else if (msg_verbose) - msg_info("remove %s", STR(actual_path)); + (void) postremove(STR(actual_path)); + /* No further work on this object is possible. */ continue; } /* + * Skip temporary message files that aren't old enough. + */ + if (MESSAGE_QUEUE(qp) && !READY_MESSAGE(st)) + /* No further work on this object is possible. */ + continue; + + /* + * Fix queueid#FIX names that were left from a previous pass over + * the queue where message queue file names were matched to their + * inode number. This requires that the file is already in the + * proper directory so that we only have to strip the suffix. + * Make sure that the name minus suffix is well formed and that + * the name matches the file inode number. + */ + if (strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) { + path[strlen(path) - SUFFIX_LEN] = 0; /* XXX */ + if (!mail_queue_id_ok(path)) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if (MESSAGE_QUEUE(qp)) { + if (sscanf(path + 5, "%lx", &inum) != 1) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if (inum != (unsigned long) st.st_ino) { + msg_warn("name/inode mismatch: %s", STR(actual_path)); + continue; + } + } + vstring_strncpy(wanted_path, STR(actual_path), + strlen(STR(actual_path)) - SUFFIX_LEN); + if (postrename(STR(actual_path), STR(wanted_path)) < 0) { + /* No further work on this object is possible. */ + continue; + } else { + if (MESSAGE_QUEUE(qp)) + inode_fixed++; + vstring_strcpy(actual_path, STR(wanted_path)); + /* At this point, path and actual_path are revalidated. */ + } + } + + /* * Skip over files with illegal names. The library routines * refuse to operate on them. */ - if (mail_queue_id_ok(path) == 0) + if (!mail_queue_id_ok(path)) { + msg_warn("bogus file name: %s", STR(actual_path)); continue; + } /* - * Skip temporary files that aren't old enough. + * Mass requeuing. The pickup daemon will copy requeued mail to a + * new queue file, so that address rewriting is applied again. + * XXX This option does not use the mail_queue(3) API, so that it + * can be run on a queue that does not have the files in the + * "right" place. It would be terribly inefficient to first have + * to rename files into place before requeuing them. Like the + * requeue_one() routine, this code does not touch logfiles. */ - if (qp->perms == MAIL_QUEUE_STAT_READY - && (st.st_mode & S_IRWXU) != qp->perms) + if ((action & ACTION_REQUEUE_ALL) + && MESSAGE_QUEUE(qp) + && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0) { + (void) mail_queue_path(wanted_path, MAIL_QUEUE_MAILDROP, path); + if (postrename(STR(actual_path), STR(wanted_path)) == 0) + message_requeued++; + /* At this point, path and actual_path are invalidated. */ continue; + } + + /* + * See if the file name matches the file inode number. Skip meta + * file directories. This option requires that meta files be put + * into their proper place before queue files, so that we can + * rename queue files and meta files at the same time. Mis-named + * files are renamed to newqueueid#FIX on the first pass, and + * upon the second pass the #FIX is stripped off the name. Of + * course we have to be prepared that the program is interrupted + * before it completes, so any left-over newqueueid#FIX files + * have to be handled properly. XXX This option does not use the + * mail_queue(3) API for message queue files, so that it can be + * run on a queue that does not have message queue files in the + * "right" place. It would be terribly inefficient to first have + * to rename files into place before fixing the file names. + */ + if ((action & ACTION_STRUCT) != 0 && MESSAGE_QUEUE(qp)) { + if (sscanf(path + 5, "%lx", &inum) != 1) { + msg_warn("bogus file name: %s", STR(actual_path)); + continue; + } + if (inum != (unsigned long) st.st_ino) { + inode_mismatch++; /* before we fix */ + fix_queue_id(STR(actual_path), queue_name, path, st.st_ino); + /* At this point, path and actual_path are invalidated. */ + continue; + } + } /* * See if this file sits in the right place in the file system * hierarchy. Its place may be wrong after a change to the - * hash_queue_{names,depth} parameter settings. The implied - * mkdir() operation is the main reason for this command to run - * with postfix privilege. The mail_queue_mkdirs() routine could - * be fixed to use the "right" privilege, but it is a good idea - * to do everything with the postfix owner privileges regardless, - * in order to limit the amount of damage that we can do. + * hash_queue_{names,depth} parameter settings. */ - (void) mail_queue_path(wanted_path, queue_name, path); - if (strcmp(STR(actual_path), STR(wanted_path)) != 0) { - if (rename(STR(actual_path), STR(wanted_path)) < 0) - if (errno != ENOENT - || mail_queue_mkdirs(STR(wanted_path)) < 0 - || rename(STR(actual_path), STR(wanted_path)) < 0) - msg_fatal("rename %s to %s: %m", STR(actual_path), - STR(wanted_path)); - if (msg_verbose) - msg_info("rename %s to %s", STR(actual_path), - STR(wanted_path)); + if (action & ACTION_STRUCT) { + (void) mail_queue_path(wanted_path, queue_name, path); + if (strcmp(STR(actual_path), STR(wanted_path)) != 0) { + position_mismatch++; /* before we fix */ + (void) postrename(STR(actual_path), STR(wanted_path)); + /* At this point, path and actual_path are invalidated. */ + continue; + } } } scan_dir_close(info); @@ -285,26 +720,43 @@ static void super(char **queues, int action) argv_free(hash_queue_names); } +/* fatal_exit - print warning if queue fix is incomplete */ + +static void fatal_exit(void) +{ + if (inode_mismatch > 0 || inode_fixed > 0 || position_mismatch > 0) + msg_fatal("OPERATION INCOMPLETE -- RERUN COMMAND TO FIX THE QUEUE FIRST"); +} + +/* interrupted - signal handler */ + +static void interrupted(int unused_sig) +{ + fatal_exit(); +} + int main(int argc, char **argv) { int fd; struct stat st; char *slash; - int debug_me = 0; int action = 0; - char **queues; + const char **queues; int c; + ARGV *requeue_names = 0; + ARGV *delete_names = 0; + char **cpp; /* * Defaults. */ static char *default_queues[] = { + MAIL_QUEUE_DEFER, /* before message directories */ + MAIL_QUEUE_BOUNCE, /* before message directories */ MAIL_QUEUE_MAILDROP, MAIL_QUEUE_INCOMING, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_DEFERRED, - MAIL_QUEUE_DEFER, - MAIL_QUEUE_BOUNCE, MAIL_QUEUE_FLUSH, 0, }; @@ -331,8 +783,6 @@ int main(int argc, char **argv) */ if (safe_getenv(CONF_ENV_VERB)) msg_verbose = 1; - if (safe_getenv(CONF_ENV_DEBUG)) - debug_me = 1; /* * Initialize. Set up logging, read the global configuration file and @@ -349,6 +799,17 @@ int main(int argc, char **argv) msg_fatal("chdir %s: %m", var_queue_dir); /* + * Be sure to log a warning if we do not finish structural repair. Maybe + * we should have an fsck-style "clean" flag so Postfix will not start + * with a broken queue. + */ + signal(SIGHUP, interrupted); + signal(SIGINT, interrupted); + signal(SIGQUIT, interrupted); + signal(SIGTERM, interrupted); + msg_cleanup(fatal_exit); + + /* * All file/directory updates must be done as the mail system owner. This * is because Postfix daemons manipulate the queue with those same * privileges, so directories must be created with the right ownership. @@ -359,22 +820,38 @@ int main(int argc, char **argv) * a non-root user limits the damage to the already compromised mail * owner. */ + if (getuid()) + msg_fatal("use of this command is reserved for the super-user"); set_ugid(var_owner_uid, var_owner_gid); /* * Parse JCL. */ - while ((c = GETOPT(argc, argv, "spv")) > 0) { + while ((c = GETOPT(argc, argv, "d:pr:sv")) > 0) { switch (c) { default: - msg_fatal("usage: %s [-s (fix structure)] [-p (purge stale files)]", + msg_fatal("usage: %s [-d queue_id (delete)] [-p (purge temporary files)] [-r queue_id (requeue)] [-s (structure fix)]", argv[0]); - case 's': - action |= ACTION_STRUCT; + case 'd': + if (delete_names == 0) + delete_names = argv_alloc(1); + argv_add(delete_names, optarg, (char *) 0); + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_DELETE_ALL : ACTION_DELETE_ONE); break; case 'p': action |= ACTION_PURGE; break; + case 'r': + if (requeue_names == 0) + requeue_names = argv_alloc(1); + argv_add(requeue_names, optarg, (char *) 0); + action |= (strcmp(optarg, "ALL") == 0 ? + ACTION_REQUEUE_ALL : ACTION_REQUEUE_ONE); + break; + case 's': + action |= ACTION_STRUCT; + break; case 'v': msg_verbose++; break; @@ -384,15 +861,100 @@ int main(int argc, char **argv) /* * Execute the explicitly specified (or default) action, on the * explicitly specified (or default) queues. + * + * XXX Work around gcc const brain damage. */ if (action == 0) action = ACTION_DEFAULT; if (argv[optind] == 0) - queues = default_queues; + queues = (const char **) default_queues; else - queues = argv + optind; + queues = (const char **) argv + optind; + +#define ACTIONS_BY_QUEUE_ID (ACTION_DELETE_ONE | ACTION_REQUEUE_ONE) + + /* + * Basic queue maintenance, as well as mass deletion, mass requeuing, and + * mass name-to-inode fixing. This ensures that queue files are in the + * right place before the file-by-name operations are done. + */ + if (action & ~ACTIONS_BY_QUEUE_ID) + super(queues, action & ~ACTIONS_BY_QUEUE_ID); - super(queues, action); + /* + * If any file names needed changing to match the message file inode + * number, those files were named newqeueid#FIX. We need a second pass to + * strip the suffix from the new queue ID. + */ + if (inode_mismatch > 0) + super(queues, 0); + + /* + * Delete queue files by name. This must not be done when queue file + * names have changed names as a result of inode number mismatches, + * because we could be deleting the wrong message. + */ + if (action & ACTION_DELETE_ONE) { + if (inode_mismatch > 0 || inode_fixed > 0) { + msg_error("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS"); + msg_fatal("CHECK YOUR QUEUE IDS AND RE-ISSUE THE COMMAND"); + } + argv_terminate(delete_names); + for (cpp = delete_names->argv; *cpp; cpp++) { + if (strcmp(*cpp, "ALL") == 0) + continue; + if (strcmp(*cpp, "-") == 0) + message_deleted += + operate_stream(VSTREAM_IN, delete_one, queues); + else + message_deleted += delete_one(queues, *cpp); + } + } + + /* + * Requeue queue files by name. This must not be done when queue file + * names have changed names as a result of inode number mismatches, + * because we could be requeuing the wrong message. + */ + if (action & ACTION_REQUEUE_ONE) { + if (inode_mismatch > 0 || inode_fixed > 0) { + msg_error("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS"); + msg_fatal("CHECK YOUR QUEUE IDS AND RE-ISSUE THE COMMAND"); + } + argv_terminate(requeue_names); + for (cpp = requeue_names->argv; *cpp; cpp++) { + if (strcmp(*cpp, "ALL") == 0) + continue; + if (strcmp(*cpp, "-") == 0) + message_requeued += + operate_stream(VSTREAM_IN, requeue_one, queues); + else + message_requeued += requeue_one(queues, *cpp); + } + } + + /* + * Report. + */ + if (message_requeued > 0) + msg_info("Requeued: %d message%s", message_requeued, + message_requeued > 1 ? "s" : ""); + if (message_deleted > 0) + msg_info("Deleted: %d message%s", message_deleted, + message_deleted > 1 ? "s" : ""); + if (inode_fixed > 0) + msg_info("Renamed to match inode number: %d message%s", inode_fixed, + inode_fixed > 1 ? "s" : ""); + if (inode_mismatch > 0 || inode_fixed > 0) + msg_warn("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS"); + + /* + * Clean up. + */ + if (requeue_names) + argv_free(requeue_names); + if (delete_names) + argv_free(delete_names); exit(0); } diff --git a/gnu/dist/postfix/src/qmgr/Makefile.in b/gnu/dist/postfix/src/qmgr/Makefile.in index c37f1d78fd0..19f24d7e67e 100644 --- a/gnu/dist/postfix/src/qmgr/Makefile.in +++ b/gnu/dist/postfix/src/qmgr/Makefile.in @@ -73,6 +73,8 @@ qmgr.o: ../../include/mail_conf.h qmgr.o: ../../include/mail_params.h qmgr.o: ../../include/mail_proto.h qmgr.o: ../../include/iostuff.h +qmgr.o: ../../include/attr.h +qmgr.o: ../../include/mail_flow.h qmgr.o: ../../include/master_proto.h qmgr.o: ../../include/mail_server.h qmgr.o: qmgr.h @@ -133,9 +135,11 @@ qmgr_deliver.o: ../../include/events.h qmgr_deliver.o: ../../include/iostuff.h qmgr_deliver.o: ../../include/mail_queue.h qmgr_deliver.o: ../../include/mail_proto.h +qmgr_deliver.o: ../../include/attr.h qmgr_deliver.o: ../../include/recipient_list.h qmgr_deliver.o: ../../include/mail_params.h qmgr_deliver.o: ../../include/deliver_request.h +qmgr_deliver.o: ../../include/verp_sender.h qmgr_deliver.o: qmgr.h qmgr_deliver.o: ../../include/scan_dir.h qmgr_deliver.o: ../../include/maps.h @@ -188,6 +192,7 @@ qmgr_message.o: ../../include/mail_addr_find.h qmgr_message.o: ../../include/maps.h qmgr_message.o: ../../include/opened.h qmgr_message.o: ../../include/resolve_local.h +qmgr_message.o: ../../include/verp_sender.h qmgr_message.o: ../../include/resolve_clnt.h qmgr_message.o: qmgr.h qmgr_message.o: ../../include/scan_dir.h @@ -252,6 +257,7 @@ qmgr_transport.o: ../../include/vstream.h qmgr_transport.o: ../../include/vbuf.h qmgr_transport.o: ../../include/iostuff.h qmgr_transport.o: ../../include/mail_proto.h +qmgr_transport.o: ../../include/attr.h qmgr_transport.o: ../../include/recipient_list.h qmgr_transport.o: ../../include/mail_conf.h qmgr_transport.o: ../../include/mail_params.h diff --git a/gnu/dist/postfix/src/qmgr/qmgr.c b/gnu/dist/postfix/src/qmgr/qmgr.c index 7513aa368e9..146b2646933 100644 --- a/gnu/dist/postfix/src/qmgr/qmgr.c +++ b/gnu/dist/postfix/src/qmgr/qmgr.c @@ -262,6 +262,7 @@ #include <mail_conf.h> #include <mail_params.h> #include <mail_proto.h> /* QMGR_SCAN constants */ +#include <mail_flow.h> /* Master process interface */ @@ -293,6 +294,8 @@ int var_qmgr_fudge; int var_qmgr_hog; int var_local_rcpt_lim; /* XXX */ int var_local_con_lim; /* XXX */ +int var_proc_limit; +bool var_verp_bounce_off; static QMGR_SCAN *qmgr_incoming; static QMGR_SCAN *qmgr_deferred; @@ -378,6 +381,8 @@ static int qmgr_loop(char *unused_name, char **unused_argv) { char *in_path = 0; char *df_path = 0; + int token_count; + int in_feed = 0; /* * This routine runs as part of the event handling loop, after the event @@ -405,11 +410,27 @@ static int qmgr_loop(char *unused_name, char **unused_argv) if (qmgr_message_count < var_qmgr_active_limit && qmgr_recipient_count < var_qmgr_rcpt_limit) if ((in_path = qmgr_scan_next(qmgr_incoming)) != 0) - qmgr_active_feed(qmgr_incoming, in_path); + in_feed = qmgr_active_feed(qmgr_incoming, in_path); if (qmgr_message_count < var_qmgr_active_limit && qmgr_recipient_count < var_qmgr_rcpt_limit) if ((df_path = qmgr_scan_next(qmgr_deferred)) != 0) qmgr_active_feed(qmgr_deferred, df_path); + + /* + * Global flow control. If enabled, slow down receiving processes that + * get ahead of the queue manager, but don't block them completely. + */ + if (var_in_flow_delay > 0) { + token_count = mail_flow_count(); + if (token_count < var_proc_limit) { + if (in_feed != 0) + mail_flow_put(1); + else if (qmgr_incoming->handle == 0) + mail_flow_put(var_proc_limit - token_count); + } else if (token_count > var_proc_limit) { + mail_flow_get(token_count - var_proc_limit); + } + } if (in_path || df_path) return (DONT_WAIT); return (WAIT_FOR_EVENT); @@ -491,10 +512,12 @@ int main(int argc, char **argv) VAR_QMGR_HOG, DEF_QMGR_HOG, &var_qmgr_hog, 10, 100, VAR_LOCAL_RCPT_LIMIT, DEF_LOCAL_RCPT_LIMIT, &var_local_rcpt_lim, 0, 0, VAR_LOCAL_CON_LIMIT, DEF_LOCAL_CON_LIMIT, &var_local_con_lim, 0, 0, + VAR_PROC_LIMIT, DEF_PROC_LIMIT, &var_proc_limit, 1, 0, 0, }; static CONFIG_BOOL_TABLE bool_table[] = { VAR_ALLOW_MIN_USER, DEF_ALLOW_MIN_USER, &var_allow_min_user, + VAR_VERP_BOUNCE_OFF, DEF_VERP_BOUNCE_OFF, &var_verp_bounce_off, 0, }; diff --git a/gnu/dist/postfix/src/qmgr/qmgr.h b/gnu/dist/postfix/src/qmgr/qmgr.h index 45ceac8e99b..9839c07c209 100644 --- a/gnu/dist/postfix/src/qmgr/qmgr.h +++ b/gnu/dist/postfix/src/qmgr/qmgr.h @@ -227,6 +227,7 @@ struct QMGR_MESSAGE { char *queue_name; /* queue name */ char *queue_id; /* queue file */ char *sender; /* complete address */ + char *verp_delims; /* VERP delimiters */ char *errors_to; /* error report address */ char *return_receipt; /* confirm receipt address */ char *filter_xport; /* filtering transport */ @@ -269,7 +270,7 @@ extern void qmgr_deliver(QMGR_TRANSPORT *, VSTREAM *); /* * qmgr_active.c */ -extern void qmgr_active_feed(QMGR_SCAN *, const char *); +extern int qmgr_active_feed(QMGR_SCAN *, const char *); extern void qmgr_active_drain(void); extern void qmgr_active_done(QMGR_MESSAGE *); diff --git a/gnu/dist/postfix/src/qmgr/qmgr_active.c b/gnu/dist/postfix/src/qmgr/qmgr_active.c index 404c4eda986..722d810739c 100644 --- a/gnu/dist/postfix/src/qmgr/qmgr_active.c +++ b/gnu/dist/postfix/src/qmgr/qmgr_active.c @@ -131,7 +131,8 @@ static void qmgr_active_corrupt(const char *queue_id) msg_warn("%s: save corrupt file queue %s id %s: %m", myname, MAIL_QUEUE_ACTIVE, queue_id); } else { - msg_warn("corrupt file queue %s id %s", MAIL_QUEUE_ACTIVE, queue_id); + msg_warn("saving corrupt file \"%s\" from queue \"%s\" to queue \"%s\"", + queue_id, MAIL_QUEUE_ACTIVE, MAIL_QUEUE_CORRUPT); } } @@ -164,7 +165,7 @@ static void qmgr_active_defer(const char *queue_name, const char *queue_id, /* qmgr_active_feed - feed one message into active queue */ -void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) +int qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) { char *myname = "qmgr_active_feed"; QMGR_MESSAGE *message; @@ -180,7 +181,7 @@ void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) * 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; + return (0); if (msg_verbose) msg_info("%s: %s", myname, path); @@ -194,7 +195,7 @@ void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) if (msg_verbose) msg_info("%s: skip %s (%ld seconds)", myname, queue_id, (long) (st.st_mtime - event_time())); - return; + return (0); } /* @@ -206,7 +207,7 @@ void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) 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; + return (0); } /* @@ -226,8 +227,10 @@ void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id, scan_info->flags)) == 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 { /* @@ -236,6 +239,7 @@ void qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id) */ if (message->refcount == 0) qmgr_active_done(message); + return (1); } } @@ -275,12 +279,21 @@ void qmgr_active_done(QMGR_MESSAGE *message) } else { if (msg_verbose) msg_info("%s: bounce %s", myname, message->queue_id); - abounce_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_2_bounce_flush, - (char *) message); + if (message->verp_delims == 0 || var_verp_bounce_off) + abounce_flush(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + 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->errors_to, + message->verp_delims, + qmgr_active_done_2_bounce_flush, + (char *) message); return; } } @@ -317,7 +330,7 @@ static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) * attributes, and by pretending that delivery was deferred. */ if (message->flags - && !mail_open_ok(MAIL_QUEUE_ACTIVE, message->queue_id, &st, &path)) { + && 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; @@ -351,14 +364,23 @@ static void qmgr_active_done_2_generic(QMGR_MESSAGE *message) */ if (message->flags) { if (event_time() > message->arrival_time + var_max_queue_time) { - if (msg_verbose) - msg_info("%s: too old, bouncing %s", myname, message->queue_id); - adefer_flush(BOUNCE_FLAG_KEEP, - message->queue_name, - message->queue_id, - message->errors_to, - qmgr_active_done_3_defer_flush, - (char *) message); + 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->errors_to, + qmgr_active_done_3_defer_flush, + (char *) message); + else + adefer_flush_verp(BOUNCE_FLAG_KEEP, + message->queue_name, + message->queue_id, + 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) { diff --git a/gnu/dist/postfix/src/qmgr/qmgr_deliver.c b/gnu/dist/postfix/src/qmgr/qmgr_deliver.c index 1a605dd1f05..45c0a6cdde0 100644 --- a/gnu/dist/postfix/src/qmgr/qmgr_deliver.c +++ b/gnu/dist/postfix/src/qmgr/qmgr_deliver.c @@ -63,6 +63,7 @@ #include <recipient_list.h> #include <mail_params.h> #include <deliver_request.h> +#include <verp_sender.h> /* Application-specific. */ @@ -86,7 +87,9 @@ static int qmgr_deliver_initial_reply(VSTREAM *stream) if (peekfd(vstream_fileno(stream)) < 0) { msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); - } else if (mail_scan(stream, "%d", &stat) != 1) { + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 1) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else { @@ -103,7 +106,10 @@ static int qmgr_deliver_final_reply(VSTREAM *stream, VSTRING *reason) if (peekfd(vstream_fileno(stream)) < 0) { msg_warn("%s: premature disconnect", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); - } else if (mail_scan(stream, "%s %d", reason, &stat) != 2) { + } else if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_WHY, reason, + ATTR_TYPE_NUM, MAIL_ATTR_STATUS, &stat, + ATTR_TYPE_END) != 2) { msg_warn("%s: malformed response", VSTREAM_PATH(stream)); return (DELIVER_STAT_CRASH); } else { @@ -119,6 +125,24 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) QMGR_RCPT *recipient; QMGR_MESSAGE *message = entry->message; char *cp; + VSTRING *sender_buf = 0; + char *sender; + int flags; + char *nexthop; + + /* + * If variable envelope return path is requested, change prefix+@origin + * into prefix+user=domain@origin. Note that with VERP there is only one + * recipient per delivery. + */ + if (message->verp_delims == 0) { + sender = message->sender; + } else { + sender_buf = vstring_alloc(100); + verp_sender(sender_buf, message->verp_delims, + message->sender, list.info->address); + sender = vstring_str(sender_buf); + } /* * With mail transports that accept only one recipient per delivery, the @@ -126,17 +150,32 @@ static int qmgr_deliver_send_request(QMGR_ENTRY *entry, VSTREAM *stream) * concurrency limits. However, the delivery agent protocol expects * nexthop only, so we must strip off the recipient local part. */ - mail_print(stream, "%d %s %s %ld %ld %s %s %s %s %ld", - message->inspect_xport ? DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT, - message->queue_name, message->queue_id, - message->data_offset, message->data_size, - (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? cp + 1 : - entry->queue->name, message->sender, - message->errors_to, message->return_receipt, - message->arrival_time); + flags = message->inspect_xport ? + DEL_REQ_FLAG_BOUNCE : DEL_REQ_FLAG_DEFLT; + nexthop = (cp = strrchr(entry->queue->name, '@')) != 0 && cp[1] ? + cp + 1 : entry->queue->name; + attr_print(stream, ATTR_FLAG_MORE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_STR, MAIL_ATTR_QUEUE, message->queue_name, + ATTR_TYPE_STR, MAIL_ATTR_QUEUEID, message->queue_id, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, message->data_offset, + ATTR_TYPE_LONG, MAIL_ATTR_SIZE, message->data_size, + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, nexthop, + ATTR_TYPE_STR, MAIL_ATTR_SENDER, sender, + ATTR_TYPE_STR, MAIL_ATTR_ERRTO, message->errors_to, + ATTR_TYPE_STR, MAIL_ATTR_RRCPT, message->return_receipt, + ATTR_TYPE_LONG, MAIL_ATTR_TIME, message->arrival_time, + ATTR_TYPE_END); + if (sender_buf != 0) + vstring_free(sender_buf); for (recipient = list.info; recipient < list.info + list.len; recipient++) - mail_print(stream, "%ld %s", recipient->offset, recipient->address); - mail_print(stream, "%s", "0"); + attr_print(stream, ATTR_FLAG_MORE, + ATTR_TYPE_LONG, MAIL_ATTR_OFFSET, recipient->offset, + ATTR_TYPE_STR, MAIL_ATTR_RECIP, recipient->address, + ATTR_TYPE_END); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_OFFSET, 0, + ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write to process (%s): %m", entry->queue->transport->name); return (-1); @@ -197,6 +236,8 @@ static void qmgr_deliver_update(int unused_event, char *context) if (status == DELIVER_STAT_CRASH) { message->flags |= DELIVER_STAT_DEFER; qmgr_transport_throttle(transport, "unknown mail transport error"); + msg_warn("transport %s failure -- see a previous warning/fatal/panic logfile record for the problem description", + transport->name); qmgr_defer_transport(transport, transport->reason); } diff --git a/gnu/dist/postfix/src/qmgr/qmgr_move.c b/gnu/dist/postfix/src/qmgr/qmgr_move.c index 7f86197ec01..a4e55f0585f 100644 --- a/gnu/dist/postfix/src/qmgr/qmgr_move.c +++ b/gnu/dist/postfix/src/qmgr/qmgr_move.c @@ -35,6 +35,7 @@ #include <sys/stat.h> #include <string.h> #include <utime.h> +#include <errno.h> /* Utility library. */ @@ -73,12 +74,21 @@ void qmgr_move(const char *src_queue, const char *dst_queue, 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) - msg_fatal("%s: update %s time stamps: %m", myname, path); + 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 (mail_queue_rename(queue_id, src_queue, dst_queue)) - msg_fatal("%s: rename %s from %s to %s: %m", - myname, queue_id, src_queue, dst_queue); if (msg_verbose) msg_info("%s: moved %s from %s to %s", myname, queue_id, src_queue, dst_queue); diff --git a/gnu/dist/postfix/src/qmqpd/.printfck b/gnu/dist/postfix/src/qmqpd/.printfck new file mode 100644 index 00000000000..66016ed453c --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/gnu/dist/postfix/src/qmqpd/Makefile.in b/gnu/dist/postfix/src/qmqpd/Makefile.in new file mode 100644 index 00000000000..5e923c5b071 --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/Makefile.in @@ -0,0 +1,117 @@ +SHELL = /bin/sh +SRCS = qmqpd.c qmqpd_state.c qmqpd_peer.c +OBJS = qmqpd.o qmqpd_state.o qmqpd_peer.o +HDRS = +TESTSRC = +WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ + -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ + -Wunused +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= qmqpd_token qmqpd_check +PROG = qmqpd +INC_DIR = ../../include +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libdns.a ../../lib/libutil.a + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +Makefile: Makefile.in + (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs && cat $?) >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +SMTPD_CHECK_OBJ = qmqpd_state.o qmqpd_peer.o + +qmqpd_token: qmqpd_token.c $(LIBS) + $(CC) $(CFLAGS) -DTEST -o $@ $@.c $(LIBS) $(SYSLIBS) + +qmqpd_check: qmqpd_check.c $(SMTPD_CHECK_OBJ) $(LIBS) + mv $@.o junk + $(CC) $(CFLAGS) -DTEST -o $@ qmqpd_check.c $(SMTPD_CHECK_OBJ) \ + $(LIBS) $(SYSLIBS) + mv junk $@.o + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk *.db *.out *.tmp + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \ + done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +tests: + +# do not edit below this line - it is generated by 'make depend' +qmqpd.o: qmqpd.c +qmqpd.o: ../../include/sys_defs.h +qmqpd.o: ../../include/msg.h +qmqpd.o: ../../include/mymalloc.h +qmqpd.o: ../../include/vstring.h +qmqpd.o: ../../include/vbuf.h +qmqpd.o: ../../include/vstream.h +qmqpd.o: ../../include/netstring.h +qmqpd.o: ../../include/dict.h +qmqpd.o: ../../include/argv.h +qmqpd.o: ../../include/mail_params.h +qmqpd.o: ../../include/record.h +qmqpd.o: ../../include/rec_type.h +qmqpd.o: ../../include/mail_proto.h +qmqpd.o: ../../include/iostuff.h +qmqpd.o: ../../include/attr.h +qmqpd.o: ../../include/cleanup_user.h +qmqpd.o: ../../include/mail_date.h +qmqpd.o: ../../include/mail_conf.h +qmqpd.o: ../../include/debug_peer.h +qmqpd.o: ../../include/mail_stream.h +qmqpd.o: ../../include/namadr_list.h +qmqpd.o: ../../include/match_list.h +qmqpd.o: ../../include/match_ops.h +qmqpd.o: ../../include/quote_822_local.h +qmqpd.o: ../../include/match_parent_style.h +qmqpd.o: ../../include/mail_server.h +qmqpd.o: qmqpd.h +qmqpd_peer.o: qmqpd_peer.c +qmqpd_peer.o: ../../include/sys_defs.h +qmqpd_peer.o: ../../include/msg.h +qmqpd_peer.o: ../../include/mymalloc.h +qmqpd_peer.o: ../../include/valid_hostname.h +qmqpd_peer.o: ../../include/stringops.h +qmqpd_peer.o: ../../include/vstring.h +qmqpd_peer.o: ../../include/vbuf.h +qmqpd_peer.o: qmqpd.h +qmqpd_peer.o: ../../include/vstream.h +qmqpd_peer.o: ../../include/mail_stream.h +qmqpd_state.o: qmqpd_state.c +qmqpd_state.o: ../../include/sys_defs.h +qmqpd_state.o: ../../include/mymalloc.h +qmqpd_state.o: ../../include/vstream.h +qmqpd_state.o: ../../include/vbuf.h +qmqpd_state.o: ../../include/vstring.h +qmqpd_state.o: ../../include/mail_stream.h +qmqpd_state.o: ../../include/cleanup_user.h +qmqpd_state.o: qmqpd.h diff --git a/gnu/dist/postfix/src/qmqpd/qmqpd.c b/gnu/dist/postfix/src/qmqpd/qmqpd.c new file mode 100644 index 00000000000..442e41433e0 --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/qmqpd.c @@ -0,0 +1,677 @@ +/*++ +/* NAME +/* qmqpd 8 +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* \fBqmqpd\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix QMQP server receives one message per connection. +/* Each message is piped through the \fBcleanup\fR(8) +/* daemon, and is placed into the \fBincoming\fR queue as one +/* single queue file. The program expects to be run from the +/* \fBmaster\fR(8) process manager. +/* +/* The QMQP server implements one access policy: only explicitly +/* authorized client hosts are allowed to use the service. +/* SECURITY +/* .ad +/* .fi +/* The QMQP server is moderately security-sensitive. It talks to QMQP +/* clients and to DNS servers on the network. The QMQP server can be +/* run chrooted at fixed low privilege. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8). +/* BUGS +/* The QMQP protocol provides only one server reply per message +/* delivery. It is therefore not possible to reject individual +/* recipients. +/* +/* The QMQP protocol requires the server to receive the entire +/* message before replying. If a message is malformed, or if any +/* netstring component is longer than acceptable, Postfix replies +/* immediately and closes the connection. It is left up to the +/* client to handle the situation. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. See the Postfix \fBmain.cf\fR file for syntax details +/* and for default values. Use the \fBpostfix reload\fR command after +/* a configuration change. +/* .SH Miscellaneous +/* .ad +/* .fi +/* .IP \fBalways_bcc\fR +/* Address to send a copy of each message that enters the system. +/* .IP \fBdebug_peer_level\fR +/* Increment in verbose logging level when a remote host matches a +/* pattern in the \fBdebug_peer_list\fR parameter. +/* .IP \fBdebug_peer_list\fR +/* List of domain or network patterns. When a remote host matches +/* a pattern, increase the verbose logging level by the amount +/* specified in the \fBdebug_peer_level\fR parameter. +/* .IP \fBhopcount_limit\fR +/* Limit the number of \fBReceived:\fR message headers. +/* .IP \fBqmqpd_authorized_clients\fR +/* A list of domain or network patterns that specifies what +/* clients are allowed to use the service. +/* .IP \fBqmqpd_timeout\fR +/* Limit the time to send a server response and to receive a client +/* request. +/* .IP \fBsoft_bounce\fR +/* Change hard (D) reject responses into soft (Z) reject responses. +/* This can be useful for testing purposes. +/* .SH "Content inspection controls" +/* .IP \fBcontent_filter\fR +/* The name of a mail delivery transport that filters mail and that +/* either bounces mail or re-injects the result back into Postfix. +/* This parameter uses the same syntax as the right-hand side of +/* a Postfix transport table. +/* .SH "Resource controls" +/* .ad +/* .fi +/* .IP \fBline_length_limit\fR +/* Limit the amount of memory in bytes used for the handling of +/* partial input lines, and the length of sender and recipient +/* addresses that are received from client. +/* .IP \fBmessage_size_limit\fR +/* Limit the total size in bytes of a message, including on-disk +/* storage for sender and recipient address information. +/* .SH Tarpitting +/* .ad +/* .fi +/* .IP \fBqmqpd_error_sleep_time\fR +/* Time to wait in seconds before informing the client of +/* a problem. This slows down run-away errors. +/* SEE ALSO +/* http://cr.yp.to/proto/qmqp.html, QMQP protocol +/* cleanup(8) message canonicalization +/* master(8) process manager +/* syslogd(8) system logging +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <ctype.h> +#include <stdarg.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <vstream.h> +#include <netstring.h> +#include <dict.h> + +/* Global library. */ + +#include <mail_params.h> +#include <record.h> +#include <rec_type.h> +#include <mail_proto.h> +#include <cleanup_user.h> +#include <mail_date.h> +#include <mail_conf.h> +#include <debug_peer.h> +#include <mail_stream.h> +#include <namadr_list.h> +#include <quote_822_local.h> +#include <match_parent_style.h> + +/* Single-threaded server skeleton. */ + +#include <mail_server.h> + +/* Application-specific */ + +#include <qmqpd.h> + + /* + * Tunable parameters. Make sure that there is some bound on the length of a + * netstring, so that the mail system stays in control even when a malicious + * client sends netstrings of unreasonable length. The recipient count limit + * is enforced by the message size limit. + */ +int var_qmqpd_timeout; +int var_qmqpd_err_sleep; +char *var_always_bcc; +char *var_filter_xport; +char *var_qmqpd_clients; + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +#define DO_LOG 1 +#define DONT_LOG 0 + + /* + * Access control. This service should be exposed only to explicitly + * authorized clients. There is no default authorization. + */ +static NAMADR_LIST *qmqpd_clients; + +/* qmqpd_open_file - open a queue file */ + +static void qmqpd_open_file(QMQPD_STATE *state) +{ + + /* + * Connect to the cleanup server. Log client name/address with queue ID. + */ + state->dest = mail_stream_service(MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP); + if (state->dest == 0 + || attr_print(state->dest->stream, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, CLEANUP_FLAG_FILTER, + ATTR_TYPE_END) != 0) + msg_fatal("unable to connect to the %s %s service", + MAIL_CLASS_PUBLIC, MAIL_SERVICE_CLEANUP); + state->cleanup = state->dest->stream; + state->queue_id = mystrdup(state->dest->id); + msg_info("%s: client=%s", state->queue_id, state->namaddr); + + /* + * Record the time of arrival. Optionally, enable content filtering (not + * bloody likely, but present for the sake of consistency with all other + * Postfix points of entrance). + */ + rec_fprintf(state->cleanup, REC_TYPE_TIME, "%ld", state->time); + if (*var_filter_xport) + rec_fprintf(state->cleanup, REC_TYPE_FILT, "%s", var_filter_xport); +} + +/* qmqpd_read_content - receive message content */ + +static void qmqpd_read_content(QMQPD_STATE *state) +{ + state->where = "receiving message content"; + netstring_get(state->client, state->message, var_message_limit); +} + +/* qmqpd_copy_sender - copy envelope sender */ + +static void qmqpd_copy_sender(QMQPD_STATE *state) +{ + char *end_prefix; + char *end_origin; + int verp_requested; + + /* + * If the sender address looks like prefix-@origin-@[], then request + * variable envelope return path delivery, with an envelope sender + * address of prefix@origin, and with VERP delimiters of - and =. This + * way, the recipients will see envelope sender addresses that look like: + * prefix-user=domain@origin. + */ + state->where = "receiving sender address"; + netstring_get(state->client, state->buf, var_line_limit); + VSTRING_TERMINATE(state->buf); + verp_requested = ((end_prefix = strstr(STR(state->buf), "-@")) != 0 + && (end_origin = strstr(end_prefix + 2, "-@")) != 0 + && strncmp(end_origin + 2, "[]", 2) == 0 + && vstring_end(state->buf) == end_origin + 4); + if (verp_requested) { + memcpy(end_prefix, end_prefix + 1, end_origin - end_prefix - 1); + vstring_truncate(state->buf, end_origin - STR(state->buf) - 1); + } + if (state->err == CLEANUP_STAT_OK + && REC_PUT_BUF(state->cleanup, REC_TYPE_FROM, state->buf) < 0) + state->err = CLEANUP_STAT_WRITE; + if (verp_requested) + if (state->err == CLEANUP_STAT_OK + && rec_put(state->cleanup, REC_TYPE_VERP, "-=", 2) < 0) + state->err = CLEANUP_STAT_WRITE; + state->sender = mystrndup(STR(state->buf), LEN(state->buf)); +} + +/* qmqpd_copy_recipients - copy message recipients */ + +static void qmqpd_copy_recipients(QMQPD_STATE *state) +{ + int ch; + + /* + * Remember the first recipient. We are done when we read the over-all + * netstring terminator. + * + * XXX This approach violates abstractions, but it is a heck of a lot more + * convenient than counting the over-all byte count down to zero, like + * qmail does. + */ + state->where = "receiving recipient address"; + while ((ch = VSTREAM_GETC(state->client)) != ',') { + vstream_ungetc(state->client, ch); + netstring_get(state->client, state->buf, var_line_limit); + if (state->err == CLEANUP_STAT_OK + && REC_PUT_BUF(state->cleanup, REC_TYPE_RCPT, state->buf) < 0) + state->err = CLEANUP_STAT_WRITE; + state->rcpt_count++; + if (state->recipient == 0) + state->recipient = mystrndup(STR(state->buf), LEN(state->buf)); + } + + /* + * Append the optional recipient who is copied on all mail. + */ + if (*var_always_bcc) + rec_fputs(state->cleanup, REC_TYPE_RCPT, var_always_bcc); +} + +/* qmqpd_next_line - get line from buffer, return last char, newline, or -1 */ + +static int qmqpd_next_line(VSTRING *message, char **start, int *len, + char **next) +{ + char *beyond = STR(message) + LEN(message); + char *enough = *next + var_line_limit; + char *cp; + + /* + * Stop at newline or at some limit. Don't look beyond the end of the + * buffer. + */ +#define UCHARPTR(x) ((unsigned char *) (x)) + + for (cp = *start = *next; /* void */ ; cp++) { + if (cp >= beyond) + return ((*len = (*next = cp) - *start) > 0 ? UCHARPTR(cp)[-1] : -1); + if (*cp == '\n') + return ((*len = cp - *start), (*next = cp + 1), '\n'); + if (cp >= enough) + return ((*len = cp - *start), (*next = cp), UCHARPTR(cp)[-1]); + } +} + +/* qmqpd_write_content - write the message content segment */ + +static void qmqpd_write_content(QMQPD_STATE *state) +{ + char *start; + char *next; + int len; + int rec_type; + int first = 1; + int ch; + + /* + * Start the message content segment. Prepend our own Received: header to + * the message content. List the recipient only when a message has one + * recipient. Otherwise, don't list the recipient to avoid revealing Bcc: + * recipients that are supposed to be invisible. + */ + rec_fputs(state->cleanup, REC_TYPE_MESG, ""); + rec_fprintf(state->cleanup, REC_TYPE_NORM, "Received: from %s (%s [%s])", + state->name, state->name, state->addr); + if (state->rcpt_count == 1 && state->recipient) { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tby %s (%s) with %s id %s", + var_myhostname, var_mail_name, + state->protocol, state->queue_id); + quote_822_local(state->buf, state->recipient); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tfor <%s>; %s", STR(state->buf), mail_date(state->time)); + } else { + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tby %s (%s) with %s", + var_myhostname, var_mail_name, state->protocol); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\tid %s; %s", state->queue_id, mail_date(state->time)); + } +#ifdef RECEIVED_ENVELOPE_FROM + quote_822_local(state->buf, state->sender); + rec_fprintf(state->cleanup, REC_TYPE_NORM, + "\t(envelope-from <%s>)", STR(state->buf)); +#endif + + /* + * Write the message content. + * + * XXX Force an empty record when the queue file content begins with + * whitespace, so that it won't be considered as being part of our own + * Received: header. What an ugly Kluge. + * + * XXX Deal with UNIX-style From_ lines at the start of message content just + * in case. + */ + for (next = STR(state->message); /* void */ ; /* void */ ) { + if ((ch = qmqpd_next_line(state->message, &start, &len, &next)) < 0) + break; + if (ch == '\n') + rec_type = REC_TYPE_NORM; + else + rec_type = REC_TYPE_CONT; + if (first) { + if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { + rec_fprintf(state->cleanup, rec_type, + "Mailbox-Line: %*s", len, start); + continue; + } + first = 0; + if (len > 0 && ISSPACE(start[0])) + rec_put(state->cleanup, REC_TYPE_NORM, "", 0); + } + if (rec_put(state->cleanup, rec_type, start, len) < 0) { + state->err = CLEANUP_STAT_WRITE; + return; + } + } +} + +/* qmqpd_close_file - close queue file */ + +static void qmqpd_close_file(QMQPD_STATE *state) +{ + + /* + * Send the end-of-segment markers. + */ + if (state->err == CLEANUP_STAT_OK) + if (rec_fputs(state->cleanup, REC_TYPE_XTRA, "") < 0 + || rec_fputs(state->cleanup, REC_TYPE_END, "") < 0 + || vstream_fflush(state->cleanup)) + state->err = CLEANUP_STAT_WRITE; + + /* + * Finish the queue file or finish the cleanup conversation. + */ + if (state->err == 0) + state->err = mail_stream_finish(state->dest, state->why_rejected); + else + mail_stream_cleanup(state->dest); + state->dest = 0; +} + +/* qmqpd_reply - send status to client and optionally log message */ + +static void qmqpd_reply(QMQPD_STATE *state, int log_message, + int status_code, const char *fmt,...) +{ + va_list ap; + + /* + * Optionally change hard errors into retryable ones. Send the reply and + * optionally log it. Always insert a delay before reporting a problem. + * This slows down software run-away conditions. + */ + if (status_code == QMQPD_STAT_HARD && var_soft_bounce) + status_code = QMQPD_STAT_RETRY; + VSTRING_RESET(state->buf); + VSTRING_ADDCH(state->buf, status_code); + va_start(ap, fmt); + vstring_vsprintf_append(state->buf, fmt, ap); + va_end(ap); + NETSTRING_PUT_BUF(state->client, state->buf); + if (log_message) + (status_code == QMQPD_STAT_OK ? msg_info : msg_warn) ("%s: %s: %s", + state->queue_id, state->namaddr, STR(state->buf) + 1); + if (status_code != QMQPD_STAT_OK) + sleep(var_qmqpd_err_sleep); + netstring_fflush(state->client); +} + +/* qmqpd_send_status - send mail transaction completion status */ + +static void qmqpd_send_status(QMQPD_STATE *state) +{ + + /* + * One message may suffer from multiple errors, so complain only about + * the most severe error. + * + * See also: smtpd.c + */ + state->where = "sending completion status"; + + if (state->err == CLEANUP_STAT_OK) { + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_OK, + "Ok: queued as %s", state->queue_id); + } else if ((state->err & CLEANUP_STAT_BAD) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: internal error %d", state->err); + } else if ((state->err & CLEANUP_STAT_SIZE) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: message too large"); + } else if ((state->err & CLEANUP_STAT_HOPS) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: too many hops"); + } else if ((state->err & CLEANUP_STAT_CONT) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: %s", STR(state->why_rejected)); + } else if ((state->err & CLEANUP_STAT_WRITE) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: queue file write error"); + } else if ((state->err & CLEANUP_STAT_RCPT) != 0) { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_HARD, + "Error: no recipients specified"); + } else { + qmqpd_reply(state, DO_LOG, QMQPD_STAT_RETRY, + "Error: internal error %d", state->err); + } +} + +/* qmqpd_receive - receive QMQP message+sender+recipients */ + +static void qmqpd_receive(QMQPD_STATE *state) +{ + + /* + * Open a queue file. This must be first so that we can simplify the + * error logging and always include the queue ID information. + */ + qmqpd_open_file(state); + + /* + * Read and ignore the over-all netstring length indicator. + */ + state->where = "receiving QMQP packet header"; + (void) netstring_get_length(state->client); + + /* + * XXX Read the message content into memory, because Postfix expects to + * store the sender before storing the message content. Fixing that + * requires changes to pickup, cleanup, qmgr, and perhaps elsewhere, so + * that will have to happen later when I have more time. However, QMQP is + * used for mailing list distribution, so the bulk of the volume is + * expected to be not message content but recipients, and recipients are + * not accumulated in memory. + */ + qmqpd_read_content(state); + + /* + * Read and write the envelope sender. + */ + qmqpd_copy_sender(state); + + /* + * Read and write the envelope recipients, including the optional big + * brother recipient. + */ + qmqpd_copy_recipients(state); + + /* + * Start the message content segment, prepend our own Received: header, + * and write the message content. + */ + qmqpd_write_content(state); + + /* + * Close the queue file. + */ + qmqpd_close_file(state); + + /* + * Report the completion status to the client. + */ + qmqpd_send_status(state); +} + +/* qmqpd_proto - speak the QMQP "protocol" */ + +static void qmqpd_proto(QMQPD_STATE *state) +{ + int status; + + netstring_setup(state->client, var_qmqpd_timeout); + + switch (status = vstream_setjmp(state->client)) { + + default: + msg_panic("qmqpd_proto: unknown status %d", status); + + case NETSTRING_ERR_EOF: + state->reason = "lost connection"; + break; + + case NETSTRING_ERR_TIME: + state->reason = "read/write timeout"; + break; + + case NETSTRING_ERR_FORMAT: + state->reason = "netstring format error"; + if (vstream_setjmp(state->client) == 0) + if (state->reason && state->where) + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", + state->reason, state->where); + break; + + case NETSTRING_ERR_SIZE: + state->reason = "netstring length exceeds storage limit"; + if (vstream_setjmp(state->client) == 0) + if (state->reason && state->where) + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, "%s while %s", + state->reason, state->where); + break; + + case 0: + qmqpd_receive(state); + break; + } + + /* + * Log abnormal session termination. Indicate the last recognized state + * before things went wrong. + */ + if (state->reason && state->where) + msg_info("%s: %s: %s while %s", + state->queue_id, state->namaddr, state->reason, state->where); +} + +/* qmqpd_service - service one client */ + +static void qmqpd_service(VSTREAM *stream, char *unused_service, char **argv) +{ + QMQPD_STATE *state; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * This routine runs when a client has connected to our network port. + * Look up and sanitize the peer name and initialize some connection- + * specific state. + */ + state = qmqpd_state_alloc(stream); + + /* + * See if we need to turn on verbose logging for this client. + */ + debug_peer_check(state->name, state->addr); + + /* + * See if we want to talk to this client at all. In all cases, log the + * connection event. + */ + if (namadr_list_match(qmqpd_clients, state->name, state->addr) == 0) { + msg_info("refused connect from %s", state->namaddr); + qmqpd_reply(state, DONT_LOG, QMQPD_STAT_HARD, + "Error: %s is not authorized to use this service", + state->namaddr); + } + + /* + * Provide the QMQP service. + */ + else { + msg_info("connect from %s", state->namaddr); + qmqpd_proto(state); + msg_info("disconnect from %s", state->namaddr); + } + + /* + * After the client has gone away, clean up whatever we have set up at + * connection time. + */ + debug_peer_restore(); + qmqpd_state_free(state); +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + if (dict_changed()) { + msg_info("lookup table has changed -- exiting"); + exit(0); + } +} + +/* pre_jail_init - pre-jail initialization */ + +static void pre_jail_init(char *unused_name, char **unused_argv) +{ + debug_peer_init(); + qmqpd_clients = + namadr_list_init(match_parent_style(VAR_QMQPD_CLIENTS), + var_qmqpd_clients); +} + +/* main - the main program */ + +int main(int argc, char **argv) +{ + static CONFIG_TIME_TABLE time_table[] = { + VAR_QMTPD_TMOUT, DEF_QMTPD_TMOUT, &var_qmqpd_timeout, 1, 0, + VAR_QMTPD_ERR_SLEEP, DEF_QMTPD_ERR_SLEEP, &var_qmqpd_err_sleep, 0, 0, + 0, + }; + static CONFIG_STR_TABLE str_table[] = { + VAR_ALWAYS_BCC, DEF_ALWAYS_BCC, &var_always_bcc, 0, 0, + VAR_FILTER_XPORT, DEF_FILTER_XPORT, &var_filter_xport, 0, 0, + VAR_QMQPD_CLIENTS, DEF_QMQPD_CLIENTS, &var_qmqpd_clients, 0, 0, + 0, + }; + + /* + * Pass control to the single-threaded service skeleton. + */ + single_server_main(argc, argv, qmqpd_service, + MAIL_SERVER_TIME_TABLE, time_table, + MAIL_SERVER_STR_TABLE, str_table, + MAIL_SERVER_PRE_INIT, pre_jail_init, + MAIL_SERVER_PRE_ACCEPT, pre_accept, + 0); +} diff --git a/gnu/dist/postfix/src/qmqpd/qmqpd.h b/gnu/dist/postfix/src/qmqpd/qmqpd.h new file mode 100644 index 00000000000..aaacb424254 --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/qmqpd.h @@ -0,0 +1,79 @@ +/*++ +/* NAME +/* qmqpd 3h +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* include "qmqpd.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <time.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * Global library. + */ +#include <mail_stream.h> + + /* + * Per-session state. + */ +typedef struct { + int err; /* error flags */ + VSTREAM *client; /* client connection */ + VSTRING *message; /* message buffer */ + VSTRING *buf; /* line buffer */ + time_t time; /* start of session */ + char *name; /* client name */ + char *addr; /* client IP address */ + char *namaddr; /* name[addr] */ + char *queue_id; /* queue file ID */ + VSTREAM *cleanup; /* cleanup server */ + MAIL_STREAM *dest; /* cleanup server */ + int rcpt_count; /* recipient count */ + char *reason; /* exception name */ + char *sender; /* sender address */ + char *recipient; /* recipient address */ + char *protocol; /* protocol name */ + char *where; /* protocol state */ + VSTRING *why_rejected; /* REJECT reason */ +} QMQPD_STATE; + + /* + * QMQP protocol status codes. + */ +#define QMQPD_STAT_OK 'K' +#define QMQPD_STAT_RETRY 'Z' +#define QMQPD_STAT_HARD 'D' + + /* + * qmqpd_state.c + */ +QMQPD_STATE *qmqpd_state_alloc(VSTREAM *); +void qmqpd_state_free(QMQPD_STATE *); + + /* + * qmqpd_peer.c + */ +void qmqpd_peer_init(QMQPD_STATE *); +void qmqpd_peer_reset(QMQPD_STATE *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ diff --git a/gnu/dist/postfix/src/qmqpd/qmqpd_peer.c b/gnu/dist/postfix/src/qmqpd/qmqpd_peer.c new file mode 100644 index 00000000000..9f0e9f2f5dd --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/qmqpd_peer.c @@ -0,0 +1,183 @@ +/*++ +/* NAME +/* qmqpd_peer 3 +/* SUMMARY +/* look up peer name/address information +/* SYNOPSIS +/* #include "qmqpd.h" +/* +/* void qmqpd_peer_init(state) +/* QMQPD_STATE *state; +/* +/* void qmqpd_peer_reset(state) +/* QMQPD_STATE *state; +/* DESCRIPTION +/* The qmqpd_peer_init() routine attempts to produce a printable +/* version of the peer name and address of the specified socket. +/* Where information is unavailable, the name and/or address +/* are set to "unknown". +/* +/* qmqpd_peer_init() updates the following fields: +/* .IP name +/* The client hostname. An unknown name is represented by the +/* string "unknown". +/* .IP addr +/* Printable representation of the client address. +/* .IP namaddr +/* String of the form: "name[addr]". +/* .PP +/* qmqpd_peer_reset() releases memory allocate by qmqpd_peer_init(). +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <stdio.h> /* strerror() */ +#include <errno.h> +#include <netdb.h> +#include <string.h> + + /* + * Older systems don't have h_errno. Even modern systems don't have + * hstrerror(). + */ +#ifdef NO_HERRNO + +static int h_errno = TRY_AGAIN; + +#define HSTRERROR(err) "Host not found" + +#else + +#define HSTRERROR(err) (\ + err == TRY_AGAIN ? "Host not found, try again" : \ + err == HOST_NOT_FOUND ? "Host not found" : \ + err == NO_DATA ? "Host name has no address" : \ + err == NO_RECOVERY ? "Name server failure" : \ + strerror(errno) \ + ) +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <valid_hostname.h> +#include <stringops.h> + +/* Global library. */ + + +/* Application-specific. */ + +#include "qmqpd.h" + +/* qmqpd_peer_init - initialize peer information */ + +void qmqpd_peer_init(QMQPD_STATE *state) +{ + struct sockaddr_in sin; + SOCKADDR_SIZE len = sizeof(sin); + struct hostent *hp; + int i; + + /* + * Look up the peer address information. + */ + if (getpeername(vstream_fileno(state->client), + (struct sockaddr *) & sin, &len) >= 0) { + errno = 0; + } + + /* + * If peer went away, give up. + */ + if (errno == ECONNRESET || errno == ECONNABORTED) { + state->name = mystrdup("unknown"); + state->addr = mystrdup("unknown"); + } + + /* + * Look up and "verify" the client hostname. + */ + else if (errno == 0 && sin.sin_family == AF_INET) { + state->addr = mystrdup(inet_ntoa(sin.sin_addr)); + hp = gethostbyaddr((char *) &(sin.sin_addr), + sizeof(sin.sin_addr), AF_INET); + if (hp == 0) { + state->name = mystrdup("unknown"); + } else if (!valid_hostname(hp->h_name, DONT_GRIPE)) { + state->name = mystrdup("unknown"); + } else { + state->name = mystrdup(hp->h_name); /* hp->name is clobbered!! */ + + /* + * Reject the hostname if it does not list the peer address. + */ +#define REJECT_PEER_NAME(state) { \ + myfree(state->name); \ + state->name = mystrdup("unknown"); \ + } + + hp = gethostbyname(state->name); /* clobbers hp->name!! */ + if (hp == 0) { + msg_warn("%s: hostname %s verification failed: %s", + state->addr, state->name, HSTRERROR(h_errno)); + REJECT_PEER_NAME(state); + } else if (hp->h_length != sizeof(sin.sin_addr)) { + msg_warn("%s: hostname %s verification failed: bad address size %d", + state->addr, state->name, hp->h_length); + REJECT_PEER_NAME(state); + } else { + for (i = 0; /* void */ ; i++) { + if (hp->h_addr_list[i] == 0) { + msg_warn("%s: address not listed for hostname %s", + state->addr, state->name); + REJECT_PEER_NAME(state); + break; + } + if (memcmp(hp->h_addr_list[i], + (char *) &sin.sin_addr, + sizeof(sin.sin_addr)) == 0) + break; /* keep peer name */ + } + } + } + } + + /* + * If it's not Internet, assume the client is local, and avoid using the + * naming service because that can hang when the machine is disconnected. + */ + else { + state->name = mystrdup("localhost"); + state->addr = mystrdup("127.0.0.1"); /* XXX bogus. */ + } + + /* + * Do the name[addr] formatting for pretty reports. + */ + state->namaddr = + concatenate(state->name, "[", state->addr, "]", (char *) 0); +} + +/* qmqpd_peer_reset - destroy peer information */ + +void qmqpd_peer_reset(QMQPD_STATE *state) +{ + myfree(state->name); + myfree(state->addr); + myfree(state->namaddr); +} diff --git a/gnu/dist/postfix/src/qmqpd/qmqpd_state.c b/gnu/dist/postfix/src/qmqpd/qmqpd_state.c new file mode 100644 index 00000000000..8e5b46c9c4e --- /dev/null +++ b/gnu/dist/postfix/src/qmqpd/qmqpd_state.c @@ -0,0 +1,98 @@ +/*++ +/* NAME +/* qmqpd_state 3 +/* SUMMARY +/* Postfix QMQP server +/* SYNOPSIS +/* #include "qmqpd.h" +/* +/* QMQPD_STATE *qmqpd_state_alloc(stream) +/* VSTREAM *stream; +/* +/* void qmqpd_state_free(state) +/* QMQPD_STATE *state; +/* DESCRIPTION +/* qmqpd_state_alloc() creates and initializes session context. +/* +/* qmqpd_state_free() destroys session context. +/* +/* Arguments: +/* .IP stream +/* Stream connected to peer. The stream is not copied. +/* DIAGNOSTICS +/* All errors are fatal. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <time.h> + +/* Utility library. */ + +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> + +/* Global library. */ + +#include <mail_stream.h> +#include <cleanup_user.h> + +/* Application-specific. */ + +#include <qmqpd.h> + +/* qmqpd_state_alloc - allocate and initialize session state */ + +QMQPD_STATE *qmqpd_state_alloc(VSTREAM *stream) +{ + QMQPD_STATE *state; + + state = (QMQPD_STATE *) mymalloc(sizeof(*state)); + state->err = CLEANUP_STAT_OK; + state->client = stream; + state->message = vstring_alloc(1000); + state->buf = vstring_alloc(100); + state->time = time((time_t *) 0); + qmqpd_peer_init(state); + state->queue_id = 0; + state->cleanup = 0; + state->dest = 0; + state->rcpt_count = 0; + state->reason = 0; + state->sender = 0; + state->recipient = 0; + state->protocol = "QMQP"; + state->where = "initializing client connection"; + state->why_rejected = vstring_alloc(10); + return (state); +} + +/* qmqpd_state_free - destroy session state */ + +void qmqpd_state_free(QMQPD_STATE *state) +{ + vstring_free(state->message); + vstring_free(state->buf); + qmqpd_peer_reset(state); + if (state->queue_id) + myfree(state->queue_id); + if (state->dest) + mail_stream_cleanup(state->dest); + if (state->sender) + myfree(state->sender); + if (state->recipient) + myfree(state->recipient); + vstring_free(state->why_rejected); + myfree((char *) state); +} diff --git a/gnu/dist/postfix/src/sendmail/Makefile.in b/gnu/dist/postfix/src/sendmail/Makefile.in index 0f9f5a4786c..e037edf1fd3 100644 --- a/gnu/dist/postfix/src/sendmail/Makefile.in +++ b/gnu/dist/postfix/src/sendmail/Makefile.in @@ -73,6 +73,7 @@ sendmail.o: ../../include/set_ugid.h sendmail.o: ../../include/connect.h sendmail.o: ../../include/mail_queue.h sendmail.o: ../../include/mail_proto.h +sendmail.o: ../../include/attr.h sendmail.o: ../../include/mail_params.h sendmail.o: ../../include/record.h sendmail.o: ../../include/rec_type.h @@ -86,4 +87,4 @@ sendmail.o: ../../include/tok822.h sendmail.o: ../../include/resolve_clnt.h sendmail.o: ../../include/mail_flush.h sendmail.o: ../../include/mail_stream.h -sendmail.o: ../../include/smtp_stream.h +sendmail.o: ../../include/verp_sender.h diff --git a/gnu/dist/postfix/src/sendmail/sendmail.c b/gnu/dist/postfix/src/sendmail/sendmail.c index a3daf6422a7..54cb2443f2d 100644 --- a/gnu/dist/postfix/src/sendmail/sendmail.c +++ b/gnu/dist/postfix/src/sendmail/sendmail.c @@ -19,11 +19,9 @@ /* /* By default, \fBsendmail\fR reads a message from standard input /* until EOF or until it reads a line with only a \fB.\fR character, -/* and arranges for delivery. \fBsendmail\fR attempts to create -/* a queue file in the \fBmaildrop\fR directory. If that directory -/* is not world-writable, the message is piped through the -/* \fBpostdrop\fR(1) command, which is expected to execute with -/* suitable privileges. +/* and arranges for delivery. \fBsendmail\fR relies on the +/* \fBpostdrop\fR(1) command to create a queue file in the \fBmaildrop\fR +/* directory. /* /* Specific command aliases are provided for other common modes of /* operation: @@ -32,7 +30,7 @@ /* size, arrival time, sender, and the recipients that still need to /* be delivered. If mail could not be delivered upon the last attempt, /* the reason for failure is shown. This mode of operation is implemented -/* by connecting to the \fBshowq\fR(8) daemon. +/* by executing the \fBpostqueue\fR(1) command. /* .IP \fBnewaliases\fR /* Initialize the alias database. If no input file is specified (with /* the \fB-oA\fR option, see below), the program processes the file(s) @@ -65,6 +63,9 @@ /* .IP \fB-I\fR /* Initialize alias database. See the \fBnewaliases\fR /* command above. +/* .IP "\fB-L \fIlabel\fR (ignored)" +/* The logging label. Use the \fBsyslog_name\fR configuration +/* parameter instead. /* .IP "\fB-N \fIdsn\fR (ignored)" /* Delivery status notification control. Currently, Postfix does /* not implement \fBDSN\fR. @@ -76,6 +77,20 @@ /* \fBdebug_peer_level\fR configuration parameters instead. /* .IP "\fB-U\fR (ignored)" /* Initial user submission. +/* .IP \fB-V\fR +/* Variable Envelope Return Path. Given an envelope sender address +/* of the form \fIowner-listname\fR@\fIorigin\fR, each recipient +/* \fIuser\fR@\fIdomain\fR receives mail with a personalized envelope +/* sender address. +/* .sp +/* By default, the personalized envelope sender address is +/* \fIowner-listname\fB+\fIuser\fB=\fIdomain\fR@\fIorigin\fR. The default +/* \fB+\fR and \fB=\fR characters are configurable with the +/* \fBdefault_verp_delimiters\fR configuration parameter. +/* .IP \fB-V\fIxy\fR +/* As \fB-V\fR, but uses \fIx\fR and \fIy\fR as the VERP delimiter +/* characters, instead of the characters specified with the +/* \fBdefault_verp_delimiters\fR configuration parameter. /* .IP \fB-bd\fR /* Go into daemon mode. This mode of operation is implemented by /* executing the \fBpostfix start\fR command. @@ -127,17 +142,17 @@ /* delivery problems are sent to, unless the message contains an /* \fBErrors-To:\fR message header. /* .IP \fB-q\fR -/* Attempt to deliver all queued mail. This is implemented by kicking the -/* \fBqmgr\fR(8) daemon. +/* Attempt to deliver all queued mail. This is implemented by +/* executing the \fBpostqueue\fR(1) command. /* .IP "\fB-q\fIinterval\fR (ignored)" /* The interval between queue runs. Use the \fBqueue_run_delay\fR /* configuration parameter instead. /* .IP \fB-qR\fIsite\fR /* Schedule immediate delivery of all mail that is queued for the named -/* \fIsite\fR. Depending on the destination, this uses "fast flush" -/* service, or it has the same effect as \fBsendmail -q\fR. -/* This is implemented by connecting to the local SMTP server. -/* See \fBsmtpd\fR(8) for more information about the "fast flush" +/* \fIsite\fR. This option accepts only \fIsite\fR names that are +/* eligible for the "fast flush" service, and is implemented by +/* executing the \fBpostqueue\fR(1) command. +/* See \fBflush\fR(8) for more information about the "fast flush" /* service. /* .IP \fB-qS\fIsite\fR /* This command is not implemented. Use the slower \fBsendmail -q\fR @@ -195,12 +210,14 @@ /* List of domain or network patterns. When a remote host matches /* a pattern, increase the verbose logging level by the amount /* specified in the \fBdebug_peer_level\fR parameter. +/* .IP \fBdefault_verp_delimiters\fR +/* The VERP delimiter characters that are used when the \fB-V\fR +/* command line option is specified without delimiter characters. /* .IP \fBfast_flush_domains\fR /* List of domains that will receive "fast flush" service (default: all -/* domains that this system is willing to relay mail to). This greatly -/* improves the performance of the SMTP \fBETRN\fR request, and of the -/* \fBsendmail -qR\fR command. For domains not in the list, Postfix simply -/* attempts to deliver all queued mail. +/* domains that this system is willing to relay mail to). This list +/* specifies the domains that Postfix accepts in the SMTP \fBETRN\fR +/* request and in the \fBsendmail -qR\fR command. /* .IP \fBfork_attempts\fR /* Number of attempts to \fBfork\fR() a process before giving up. /* .IP \fBfork_delay\fR @@ -220,16 +237,17 @@ /* directory of Postfix daemons that run chrooted. /* .IP \fBqueue_run_delay\fR /* The time between successive scans of the deferred queue. +/* .IP \fBverp_delimiter_filter\fR +/* The characters that Postfix accepts as VERP delimiter characters. /* SEE ALSO /* pickup(8) mail pickup daemon /* postalias(1) maintain alias database -/* postdrop(1) privileged posting agent +/* postdrop(1) mail posting utility /* postfix(1) mail system control -/* postkick(1) kick a Postfix daemon +/* postqueue(1) mail queue control /* qmgr(8) queue manager -/* showq(8) list mail queue /* smtpd(8) SMTP server -/* flushd(8) fast flush service +/* flush(8) fast flush service /* syslogd(8) system logging /* LICENSE /* .ad @@ -257,6 +275,7 @@ #include <errno.h> #include <ctype.h> #include <stdarg.h> +#include <sysexits.h> /* Utility library. */ @@ -291,7 +310,7 @@ #include <tok822.h> #include <mail_flush.h> #include <mail_stream.h> -#include <smtp_stream.h> +#include <verp_sender.h> /* Application-specific. */ @@ -306,13 +325,6 @@ #define SM_MODE_FLUSHQ 6 /* user (stand-alone) mode */ /* - * Queue file name. Global, so that the cleanup routine can find it when - * called by the run-time error handler. - */ -static char *sendmail_path; -static void sendmail_cleanup(void); - - /* * Flag parade. */ #define SM_FLAG_AEOF (1<<0) /* archaic EOF */ @@ -320,6 +332,11 @@ static void sendmail_cleanup(void); #define SM_FLAG_DEFAULT (SM_FLAG_AEOF) /* + * VERP support. + */ +char *verp_delims; + + /* * Silly little macros (SLMs). */ #define STR vstring_str @@ -345,7 +362,6 @@ static void enqueue(const int flags, const char *sender, const char *full_name, char *postdrop_command; uid_t uid = getuid(); int status; - struct stat st; int naddr; /* @@ -370,31 +386,21 @@ static void enqueue(const int flags, const char *sender, const char *full_name, msg_warn("-f option specified malformed sender: %s", sender); } else { if ((sender = username()) == 0) - msg_fatal("unable to find out your login name"); + msg_fatal_status(EX_OSERR, "unable to find out your login name"); saved_sender = mystrdup(sender); } /* - * Open the queue file. Save the queue file name, so the run-time error - * handler can clean up in case of errors. - * - * If the queue is not world-writable, let the postdrop command open the - * queue file. + * Let the postdrop command open the queue file for us, and sanity check + * the content. XXX Make postdrop a manifest constant. */ - if (stat(MAIL_QUEUE_MAILDROP, &st) < 0) - msg_fatal("No maildrop directory %s: %m", MAIL_QUEUE_MAILDROP); - if (st.st_mode & S_IWOTH) { - handle = mail_stream_file(MAIL_QUEUE_MAILDROP, - MAIL_CLASS_PUBLIC, MAIL_SERVICE_PICKUP); - sendmail_path = mystrdup(VSTREAM_PATH(handle->stream)); - } else { - postdrop_command = concatenate(var_command_dir, "/postdrop", + errno = 0; + postdrop_command = concatenate(var_command_dir, "/postdrop -r", msg_verbose ? " -v" : (char *) 0, (char *) 0); - if ((handle = mail_stream_command(postdrop_command)) == 0) - msg_fatal("%s(%ld): unable to execute %s", - saved_sender, (long) uid, postdrop_command); - myfree(postdrop_command); - } + if ((handle = mail_stream_command(postdrop_command)) == 0) + msg_fatal_status(EX_UNAVAILABLE, "%s(%ld): unable to execute %s: %m", + saved_sender, (long) uid, postdrop_command); + myfree(postdrop_command); dst = handle->stream; /* @@ -414,6 +420,10 @@ static void enqueue(const int flags, const char *sender, const char *full_name, if (full_name || (full_name = fullname()) != 0) rec_fputs(dst, REC_TYPE_FULL, full_name); rec_fputs(dst, REC_TYPE_FROM, saved_sender); + if (verp_delims && *saved_sender == 0) + msg_fatal("-V option requires non-null sender address"); + if (verp_delims) + rec_fputs(dst, REC_TYPE_VERP, verp_delims); if (recipients) { for (cpp = recipients; *cpp != 0; cpp++) { tree = tok822_parse(*cpp); @@ -421,8 +431,9 @@ static void enqueue(const int flags, const char *sender, const char *full_name, if (tp->type == TOK822_ADDR) { tok822_internalize(buf, tp->head, TOK822_STR_DEFL); if (REC_PUT_BUF(dst, REC_TYPE_RCPT, buf) < 0) - msg_fatal("%s(%ld): error writing queue file: %m", - saved_sender, (long) uid); + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); } } tok822_free_tree(tree); @@ -463,8 +474,9 @@ static void enqueue(const int flags, const char *sender, const char *full_name, if ((flags & SM_FLAG_AEOF) && VSTRING_LEN(buf) == 1 && *STR(buf) == '.') break; if (REC_PUT_BUF(dst, type, buf) < 0) - msg_fatal("%s(%ld): error writing queue file: %m", - saved_sender, (long) uid); + msg_fatal_status(EX_TEMPFAIL, + "%s(%ld): error writing queue file: %m", + saved_sender, (long) uid); } /* @@ -485,15 +497,13 @@ static void enqueue(const int flags, const char *sender, const char *full_name, * handler from removing the file. */ if (vstream_ferror(VSTREAM_IN)) - msg_fatal("%s(%ld): error reading input: %m", - saved_sender, (long) uid); - if ((status = mail_stream_finish(handle)) != 0) - msg_fatal("%s(%ld): %s", saved_sender, - (long) uid, cleanup_strerror(status)); - if (sendmail_path) { - myfree(sendmail_path); - sendmail_path = 0; - } + msg_fatal_status(EX_DATAERR, "%s(%ld): error reading input: %m", + saved_sender, (long) uid); + if ((status = mail_stream_finish(handle, (VSTRING *) 0)) != 0) + msg_fatal_status((status & CLEANUP_STAT_BAD) ? EX_SOFTWARE : + (status & CLEANUP_STAT_WRITE) ? EX_TEMPFAIL : + EX_UNAVAILABLE, "%s(%ld): %s", saved_sender, + (long) uid, cleanup_strerror(status)); /* * Cleanup. Not really necessary as we're about to exit, but good for @@ -503,168 +513,6 @@ static void enqueue(const int flags, const char *sender, const char *full_name, myfree(saved_sender); } -/* show_queue - show queue status */ - -static void show_queue(void) -{ - char buf[VSTREAM_BUFSIZE]; - VSTREAM *showq; - int n; - - /* - * Connect to the show queue service. Terminate silently when piping into - * a program that terminates early. - */ - signal(SIGPIPE, SIG_DFL); - if ((showq = mail_connect(MAIL_CLASS_PUBLIC, MAIL_SERVICE_SHOWQ, BLOCKING)) != 0) { - while ((n = vstream_fread(showq, buf, sizeof(buf))) > 0) - if (vstream_fwrite(VSTREAM_OUT, buf, n) != n - || vstream_fflush(VSTREAM_OUT) != 0) - msg_fatal("write error: %m"); - - if (vstream_fclose(showq)) - msg_warn("close: %m"); - } - - /* - * When the mail system is down, the superuser can still access the queue - * directly. Just run the showq program in stand-alone mode. - */ - else if (geteuid() == 0) { - ARGV *argv; - int stat; - - msg_warn("Mail system is down -- accessing queue directly"); - argv = argv_alloc(6); - argv_add(argv, MAIL_SERVICE_SHOWQ, "-c", "-u", "-S", (char *) 0); - for (n = 0; n < msg_verbose; n++) - argv_add(argv, "-v", (char *) 0); - argv_terminate(argv); - stat = mail_run_foreground(var_daemon_dir, argv->argv); - argv_free(argv); - } - - /* - * When the mail system is down, unprivileged users are stuck, because by - * design the mail system contains no set_uid programs. The only way for - * an unprivileged user to cross protection boundaries is to talk to the - * showq daemon. - */ - else { - msg_fatal("Queue report unavailable - mail system is down"); - } -} - -/* flush_queue - force delivery */ - -static void flush_queue(void) -{ - - /* - * Trigger the flush queue service. - */ - if (mail_flush_deferred() < 0) - msg_warn("Cannot flush mail queue - mail system is down"); -} - -/* chat - send command and examine reply */ - -static void chat(VSTREAM *fp, VSTRING *buf, const char *fmt,...) -{ - va_list ap; - - smtp_get(buf, fp, var_line_limit); - if (STR(buf)[0] != '2') - msg_fatal("server rejected request: %s", STR(buf)); - - if (msg_verbose) - msg_info("<<< %s", STR(buf)); - - if (msg_verbose) { - va_start(ap, fmt); - vstring_vsprintf(buf, fmt, ap); - va_end(ap); - msg_info(">>> %s", STR(buf)); - } - va_start(ap, fmt); - smtp_vprintf(fp, fmt, ap); - va_end(ap); -} - -/* flush_site - flush mail for site */ - -static void flush_site(const char *site) -{ - VSTRING *buf = vstring_alloc(10); - VSTREAM *fp; - int sock; - int status; - - /* - * Make connection to the local SMTP server. Translate "connection - * refused" into something less misleading. - */ - vstring_sprintf(buf, "%s:smtp", var_myhostname); - if ((sock = inet_connect(STR(buf), BLOCKING, 10)) < 0) { - if (errno == ECONNREFUSED) - msg_fatal("mail service at %s is down", var_myhostname); - msg_fatal("connect to mail service at %s: %m", var_myhostname); - } - fp = vstream_fdopen(sock, O_RDWR); - - /* - * Prepare for trouble. - */ - vstream_control(fp, VSTREAM_CTL_EXCEPT, VSTREAM_CTL_END); - status = vstream_setjmp(fp); - if (status != 0) { - switch (status) { - case SMTP_ERR_EOF: - msg_fatal("server at %s aborted connection", var_myhostname); - case SMTP_ERR_TIME: - msg_fatal("timeout while talking to server at %s", var_myhostname); - } - } - smtp_timeout_setup(fp, 60); - - /* - * Chat with the SMTP server. - */ - chat(fp, buf, "helo %s", var_myhostname); - chat(fp, buf, "etrn %s", site); - chat(fp, buf, "quit"); - - vstream_fclose(fp); - vstring_free(buf); -} - -/* sendmail_cleanup - callback for the runtime error handler */ - -static void sendmail_cleanup(void) -{ - - /* - * We're possibly running from a signal handler, so we should not be - * doing complicated things such as memory of buffer management, but if - * for some reason we can't cleanup it is even worse to just die quietly. - */ - if (sendmail_path) { - if (remove(sendmail_path)) - msg_warn("sendmail_cleanup: remove %s: %m", sendmail_path); - else if (msg_verbose) - msg_info("remove %s", sendmail_path); - sendmail_path = 0; - } -} - -/* sendmail_sig - catch signal and clean up */ - -static void sendmail_sig(int sig) -{ - sendmail_cleanup(); - exit(sig); -} - /* main - the main program */ int main(int argc, char **argv) @@ -697,7 +545,7 @@ int main(int argc, char **argv) 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"); + msg_fatal_status(EX_UNAVAILABLE, "open /dev/null: %m"); /* * The CDE desktop calendar manager leaks a parent file descriptor into @@ -745,15 +593,16 @@ int main(int argc, char **argv) */ mail_conf_read(); if (chdir(var_queue_dir)) - msg_fatal("chdir %s: %m", var_queue_dir); + msg_fatal_status(EX_UNAVAILABLE, "chdir %s: %m", var_queue_dir); - signal(SIGPIPE, SIG_IGN); + /* + * Stop run-away process accidents by limiting the queue file size. This + * is not a defense against DOS attack. + */ + if (var_message_limit > 0 && get_file_limit() > var_message_limit) + set_file_limit((off_t) var_message_limit); - signal(SIGHUP, sendmail_sig); - signal(SIGINT, sendmail_sig); - signal(SIGQUIT, sendmail_sig); - signal(SIGTERM, sendmail_sig); - msg_cleanup(sendmail_cleanup); + signal(SIGPIPE, SIG_IGN); /* * Optionally start the debugger on ourself. This must be done after @@ -794,7 +643,12 @@ int main(int argc, char **argv) optind++; continue; } - if ((c = GETOPT(argc, argv, "B:C:F:GIN:R:UX:b:ce:f:h:imno:p:r:q:tvx")) <= 0) + if (strcmp(argv[OPTIND], "-V") == 0) { + verp_delims = var_verp_delims; + optind++; + continue; + } + if ((c = GETOPT(argc, argv, "B:C:F:GIL:N:R:UV:X:b:ce:f:h:imno:p:r:q:tvx")) <= 0) break; switch (c) { default: @@ -802,25 +656,23 @@ int main(int argc, char **argv) msg_info("-%c option ignored", c); break; case 'n': - msg_fatal("-%c option not supported", c); - case 'B': /* body type */ - break; + msg_fatal_status(EX_USAGE, "-%c option not supported", c); case 'F': /* full name */ full_name = optarg; break; - case 'G': /* gateway submission */ - break; case 'I': /* newaliases */ mode = SM_MODE_NEWALIAS; break; - case 'N': /* DSN */ - break; - case 'R': /* DSN */ + case 'V': /* VERP */ + if (verp_delims_verify(optarg) != 0) + msg_fatal_status(EX_USAGE, "-V requires two characters from %s", + var_verp_filter); + verp_delims = optarg; break; case 'b': switch (*optarg) { default: - msg_fatal("unsupported: -%c%c", c, *optarg); + msg_fatal_status(EX_USAGE, "unsupported: -%c%c", c, *optarg); case 'd': /* daemon mode */ if (mode == SM_MODE_FLUSHQ) msg_warn("ignoring -q option in daemon mode"); @@ -854,7 +706,7 @@ int main(int argc, char **argv) break; case 'A': if (optarg[1] == 0) - msg_fatal("-oA requires pathname"); + msg_fatal_status(EX_USAGE, "-oA requires pathname"); myfree(var_alias_db_map); var_alias_db_map = mystrdup(optarg + 1); set_mail_conf_str(VAR_ALIAS_DB_MAP, var_alias_db_map); @@ -881,8 +733,11 @@ int main(int argc, char **argv) } } else if (optarg[0] == 'R') { site_to_flush = optarg + 1; + if (*site_to_flush == 0) + msg_fatal("specify: -qRsitename"); } else { - msg_fatal("-q%c is not implemented", optarg[0]); + msg_fatal_status(EX_USAGE, "-q%c is not implemented", + optarg[0]); } break; case 't': @@ -892,7 +747,7 @@ int main(int argc, char **argv) msg_verbose++; break; case '?': - msg_fatal("usage: %s [options]", argv[0]); + msg_fatal_status(EX_USAGE, "usage: %s [options]", argv[0]); } } @@ -909,29 +764,47 @@ int main(int argc, char **argv) msg_fatal("cannot handle command-line recipients with -t"); /* - * Start processing. Some modes are implemented internally (enqueue - * message), or as network clients (show queue, flush queue); everything - * else is delegated to external commands. + * Start processing. Everything is delegated to external commands. */ switch (mode) { default: msg_panic("unknown operation mode: %d", mode); /* NOTREACHED */ case SM_MODE_ENQUEUE: - if (site_to_flush) - flush_site(site_to_flush); - else + if (site_to_flush == 0) { enqueue(flags, sender, full_name, argv + OPTIND); - exit(0); + exit(0); + } + if (argv[OPTIND]) + msg_fatal("flush site requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-s", site_to_flush, (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ break; case SM_MODE_MAILQ: - show_queue(); - exit(0); - break; + if (argv[OPTIND]) + msg_fatal("display queue mode requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-p", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ case SM_MODE_FLUSHQ: - flush_queue(); - exit(0); - break; + if (argv[OPTIND]) + msg_fatal("flush queue mode requires no recipient"); + ext_argv = argv_alloc(2); + argv_add(ext_argv, "postqueue", "-f", (char *) 0); + for (n = 0; n < msg_verbose; n++) + argv_add(ext_argv, "-v", (char *) 0); + argv_terminate(ext_argv); + mail_run_replace(var_command_dir, ext_argv->argv); + /* NOTREACHED */ case SM_MODE_DAEMON: if (argv[OPTIND]) msg_fatal("daemon mode requires no recipient"); @@ -955,6 +828,7 @@ int main(int argc, char **argv) for (n = 0; n < msg_verbose; n++) argv_add(ext_argv, "-v", (char *) 0); argv_split_append(ext_argv, var_alias_db_map, ", \t\r\n"); + argv_terminate(ext_argv); mail_run_replace(var_command_dir, ext_argv->argv); /* NOTREACHED */ case SM_MODE_USER: diff --git a/gnu/dist/postfix/src/showq/Makefile.in b/gnu/dist/postfix/src/showq/Makefile.in index 23d15c420d5..5c714ef1145 100644 --- a/gnu/dist/postfix/src/showq/Makefile.in +++ b/gnu/dist/postfix/src/showq/Makefile.in @@ -68,6 +68,7 @@ showq.o: ../../include/mail_queue.h showq.o: ../../include/mail_open_ok.h showq.o: ../../include/mail_proto.h showq.o: ../../include/iostuff.h +showq.o: ../../include/attr.h showq.o: ../../include/mail_date.h showq.o: ../../include/mail_params.h showq.o: ../../include/mail_scan_dir.h diff --git a/gnu/dist/postfix/src/showq/showq.c b/gnu/dist/postfix/src/showq/showq.c index 55cab747a91..97147adeae8 100644 --- a/gnu/dist/postfix/src/showq/showq.c +++ b/gnu/dist/postfix/src/showq/showq.c @@ -92,6 +92,7 @@ int var_dup_filter_limit; #define STRING_FORMAT "%-10s %8s %-20s %s\n" #define DATA_FORMAT "%-10s%c%8ld %20.20s %s\n" +#define DROP_FORMAT "%-10s%c%8ld %20.20s (maildrop queue, sender UID %u)\n" static void showq_reasons(VSTREAM *, BOUNCE_LOG *, HTABLE *); @@ -106,6 +107,7 @@ static void showq_report(VSTREAM *client, char *queue, char *id, BOUNCE_LOG *logfile; HTABLE *dup_filter = 0; char status = (strcmp(queue, MAIL_QUEUE_ACTIVE) == 0 ? '*' : ' '); + long offset; while (!vstream_ferror(client) && (rec_type = rec_get(qfile, buf, 0)) > 0) { start = vstring_str(buf); @@ -114,7 +116,8 @@ static void showq_report(VSTREAM *client, char *queue, char *id, arrival_time = atol(start); break; case REC_TYPE_SIZE: - msg_size = atol(start); + if ((msg_size = atol(start)) <= 0) + msg_size = size; break; case REC_TYPE_FROM: if (*start == 0) @@ -132,7 +135,8 @@ static void showq_report(VSTREAM *client, char *queue, char *id, "", "", "", printable(start, '?')); break; case REC_TYPE_MESG: - if (vstream_fseek(qfile, atol(start), SEEK_SET) < 0) + if ((offset = atol(start)) > 0 + && vstream_fseek(qfile, offset, SEEK_SET) < 0) msg_fatal("seek file %s: %m", VSTREAM_PATH(qfile)); break; case REC_TYPE_END: @@ -203,7 +207,6 @@ static void showq_reasons(VSTREAM *client, BOUNCE_LOG *bp, HTABLE *dup_filter) static void showq_service(VSTREAM *client, char *unused_service, char **argv) { - char **queue; VSTREAM *qfile; const char *path; int status; @@ -211,11 +214,17 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv) int file_count; unsigned long queue_size = 0; struct stat st; - char *queue_names[] = { /* XXX configurable */ - MAIL_QUEUE_INCOMING, - MAIL_QUEUE_ACTIVE, - MAIL_QUEUE_DEFERRED, - /* No maildrop until we can disable recursive scans. */ + struct queue_info { + char *name; /* queue name */ + char *(*scan_next) (SCAN_DIR *); /* flat or recursive */ + }; + struct queue_info *qp; + + static struct queue_info queue_info[] = { + MAIL_QUEUE_MAILDROP, scan_dir_next, + MAIL_QUEUE_INCOMING, mail_scan_dir_next, + MAIL_QUEUE_ACTIVE, mail_scan_dir_next, + MAIL_QUEUE_DEFERRED, mail_scan_dir_next, 0, }; @@ -231,11 +240,11 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv) * mis-configured, and force backoff by raising a fatal error. */ file_count = 0; - for (queue = queue_names; *queue != 0; queue++) { - SCAN_DIR *scan = scan_dir_open(*queue); + for (qp = queue_info; qp->name != 0; qp++) { + SCAN_DIR *scan = scan_dir_open(qp->name); char *saved_id = 0; - while ((id = mail_scan_dir_next(scan)) != 0) { + while ((id = qp->scan_next(scan)) != 0) { /* * XXX I have seen showq loop on the same queue id. That would be @@ -244,13 +253,13 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv) */ if (saved_id) { if (strcmp(saved_id, id) == 0) { - msg_warn("readdir loop on queue %s id %s", *queue, id); + msg_warn("readdir loop on queue %s id %s", qp->name, id); break; } myfree(saved_id); } saved_id = mystrdup(id); - status = mail_open_ok(*queue, id, &st, &path); + status = mail_open_ok(qp->name, id, &st, &path); if (status == MAIL_OPEN_YES) { if (file_count == 0) vstream_fprintf(client, STRING_FORMAT, @@ -259,19 +268,19 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv) "-Sender/Recipient-------"); else vstream_fprintf(client, "\n"); - if ((qfile = mail_queue_open(*queue, id, O_RDONLY, 0)) != 0) { + if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) { queue_size += st.st_size; - showq_report(client, *queue, id, qfile, (long) st.st_size); + showq_report(client, qp->name, id, qfile, (long) st.st_size); if (vstream_fclose(qfile)) - msg_warn("close file %s %s: %m", *queue, id); - } else if (strcmp(*queue, MAIL_QUEUE_MAILDROP) == 0) { + msg_warn("close file %s %s: %m", qp->name, id); + } else if (strcmp(qp->name, MAIL_QUEUE_MAILDROP) == 0) { queue_size += st.st_size; - vstream_fprintf(client, DATA_FORMAT, id, ' ', + vstream_fprintf(client, DROP_FORMAT, id, ' ', (long) st.st_size, asctime(localtime(&st.st_mtime)), - "(to be determined)"); + (unsigned) st.st_uid); } else if (errno != ENOENT) - msg_fatal("open %s %s: %m", *queue, id); + msg_fatal("open %s %s: %m", qp->name, id); file_count++; vstream_fflush(client); } diff --git a/gnu/dist/postfix/src/smtp/Makefile.in b/gnu/dist/postfix/src/smtp/Makefile.in index ed46099b665..035cd5acab4 100644 --- a/gnu/dist/postfix/src/smtp/Makefile.in +++ b/gnu/dist/postfix/src/smtp/Makefile.in @@ -80,6 +80,7 @@ smtp.o: ../../include/mail_error.h smtp.o: ../../include/deliver_pass.h smtp.o: ../../include/mail_proto.h smtp.o: ../../include/iostuff.h +smtp.o: ../../include/attr.h smtp.o: ../../include/mail_server.h smtp.o: smtp.h smtp.o: smtp_sasl.h @@ -91,6 +92,7 @@ smtp_addr.o: ../../include/vbuf.h smtp_addr.o: ../../include/mymalloc.h smtp_addr.o: ../../include/inet_addr_list.h smtp_addr.o: ../../include/stringops.h +smtp_addr.o: ../../include/myrand.h smtp_addr.o: ../../include/mail_params.h smtp_addr.o: ../../include/own_inet_addr.h smtp_addr.o: ../../include/dns.h @@ -149,6 +151,7 @@ smtp_proto.o: ../../include/vstream.h smtp_proto.o: ../../include/vstring_vstream.h smtp_proto.o: ../../include/stringops.h smtp_proto.o: ../../include/mymalloc.h +smtp_proto.o: ../../include/iostuff.h smtp_proto.o: ../../include/mail_params.h smtp_proto.o: ../../include/smtp_stream.h smtp_proto.o: ../../include/mail_queue.h @@ -177,6 +180,8 @@ smtp_sasl_glue.o: ../../include/split_at.h smtp_sasl_glue.o: ../../include/name_mask.h smtp_sasl_glue.o: ../../include/mail_params.h smtp_sasl_glue.o: ../../include/string_list.h +smtp_sasl_glue.o: ../../include/match_list.h +smtp_sasl_glue.o: ../../include/match_ops.h smtp_sasl_glue.o: ../../include/maps.h smtp_sasl_glue.o: ../../include/dict.h smtp_sasl_glue.o: ../../include/vstream.h diff --git a/gnu/dist/postfix/src/smtp/smtp.c b/gnu/dist/postfix/src/smtp/smtp.c index 477e33b7197..9c61d89dbba 100644 --- a/gnu/dist/postfix/src/smtp/smtp.c +++ b/gnu/dist/postfix/src/smtp/smtp.c @@ -41,6 +41,7 @@ /* RFC 1870 (Message Size Declaration) /* RFC 2197 (Pipelining) /* RFC 2554 (AUTH command) +/* RFC 2821 (SMTP protocol) /* DIAGNOSTICS /* Problems and transactions are logged to \fBsyslogd\fR(8). /* Corrupted message files are marked so that the queue manager can @@ -94,14 +95,23 @@ /* Always send EHLO at the start of a connection. /* .IP \fBsmtp_never_send_ehlo\fR /* Never send EHLO at the start of a connection. +/* .IP \fBsmtp_bind_address\fR +/* Numerical source network address to bind to when making a connection. +/* .IP \fBsmtp_break_lines\fR +/* Break lines > \fB$line_length_limit\fR into multiple shorter lines. +/* Some SMTP servers misbehave on long lines. /* .IP \fBsmtp_skip_4xx_greeting\fR /* Skip servers that greet us with a 4xx status code. /* .IP \fBsmtp_skip_5xx_greeting\fR /* Skip servers that greet us with a 5xx status code. /* .IP \fBsmtp_skip_quit_response\fR /* Do not wait for the server response after sending QUIT. -/* .IP \fBsmtp_bind_address\fR -/* Numerical network address to bind to when making a connection. +/* .IP \fBsmtp_pix_workaround_delay_time\fR +/* The time to pause before sending .<CR><LF>, while working +/* around the CISCO PIX firewall <CR><LF>.<CR><LF> bug. +/* .IP \fBsmtp_pix_workaround_threshold_time\fR +/* The time a message must be queued before the CISCO PIX firewall +/* <CR><LF>.<CR><LF> bug workaround is turned on. /* .SH "Authentication controls" /* .IP \fBsmtp_enable_sasl_auth\fR /* Enable per-session authentication as per RFC 2554 (SASL). @@ -234,8 +244,6 @@ int var_smtp_data1_tmout; int var_smtp_data2_tmout; int var_smtp_quit_tmout; char *var_inet_interfaces; -char *var_debug_peer_list; -int var_debug_peer_level; char *var_notify_classes; int var_smtp_skip_4xx_greeting; int var_smtp_skip_5xx_greeting; @@ -250,6 +258,10 @@ char *var_smtp_sasl_opts; char *var_smtp_sasl_passwd; bool var_smtp_sasl_enable; char *var_smtp_bind_addr; +bool var_smtp_rand_addr; +bool var_smtp_break_lines; +int var_smtp_pix_thresh; +int var_smtp_pix_delay; /* * Global variables. smtp_errno is set by the address lookup routines and by @@ -394,7 +406,6 @@ static void pre_exit(void) int main(int argc, char **argv) { static CONFIG_STR_TABLE str_table[] = { - VAR_DEBUG_PEER_LIST, DEF_DEBUG_PEER_LIST, &var_debug_peer_list, 0, 0, VAR_NOTIFY_CLASSES, DEF_NOTIFY_CLASSES, &var_notify_classes, 0, 0, VAR_FALLBACK_RELAY, DEF_FALLBACK_RELAY, &var_fallback_relay, 0, 0, VAR_BESTMX_TRANSP, DEF_BESTMX_TRANSP, &var_bestmx_transp, 0, 0, @@ -413,10 +424,11 @@ int main(int argc, char **argv) VAR_SMTP_DATA1_TMOUT, DEF_SMTP_DATA1_TMOUT, &var_smtp_data1_tmout, 1, 0, VAR_SMTP_DATA2_TMOUT, DEF_SMTP_DATA2_TMOUT, &var_smtp_data2_tmout, 1, 0, VAR_SMTP_QUIT_TMOUT, DEF_SMTP_QUIT_TMOUT, &var_smtp_quit_tmout, 1, 0, + VAR_SMTP_PIX_THRESH, DEF_SMTP_PIX_THRESH, &var_smtp_pix_thresh, 0, 0, + VAR_SMTP_PIX_DELAY, DEF_SMTP_PIX_DELAY, &var_smtp_pix_delay, 1, 0, 0, }; static CONFIG_INT_TABLE int_table[] = { - VAR_DEBUG_PEER_LEVEL, DEF_DEBUG_PEER_LEVEL, &var_debug_peer_level, 1, 0, 0, }; static CONFIG_BOOL_TABLE bool_table[] = { @@ -427,6 +439,8 @@ int main(int argc, char **argv) VAR_SMTP_ALWAYS_EHLO, DEF_SMTP_ALWAYS_EHLO, &var_smtp_always_ehlo, VAR_SMTP_NEVER_EHLO, DEF_SMTP_NEVER_EHLO, &var_smtp_never_ehlo, VAR_SMTP_SASL_ENABLE, DEF_SMTP_SASL_ENABLE, &var_smtp_sasl_enable, + VAR_SMTP_RAND_ADDR, DEF_SMTP_RAND_ADDR, &var_smtp_rand_addr, + VAR_SMTP_BREAK_LINES, DEF_SMTP_BREAK_LINES, &var_smtp_break_lines, 0, }; diff --git a/gnu/dist/postfix/src/smtp/smtp.h b/gnu/dist/postfix/src/smtp/smtp.h index 8c6dd5b0636..81990e8a2f5 100644 --- a/gnu/dist/postfix/src/smtp/smtp.h +++ b/gnu/dist/postfix/src/smtp/smtp.h @@ -52,6 +52,7 @@ typedef struct SMTP_STATE { VSTRING *sasl_decoded; /* decoding buffer */ sasl_callback_t *sasl_callbacks; /* stateful callbacks */ #endif + off_t size_limit; /* server limit or unknown */ } SMTP_STATE; #define SMTP_FEATURE_ESMTP (1<<0) @@ -60,6 +61,7 @@ typedef struct SMTP_STATE { #define SMTP_FEATURE_SIZE (1<<3) #define SMTP_FEATURE_STARTTLS (1<<4) #define SMTP_FEATURE_AUTH (1<<5) +#define SMTP_FEATURE_MAYBEPIX (1<<6) /* PIX smtp fixup mode */ /* * smtp.c diff --git a/gnu/dist/postfix/src/smtp/smtp_chat.c b/gnu/dist/postfix/src/smtp/smtp_chat.c index 62c0de64f3f..d33a90e4e04 100644 --- a/gnu/dist/postfix/src/smtp/smtp_chat.c +++ b/gnu/dist/postfix/src/smtp/smtp_chat.c @@ -252,7 +252,7 @@ void smtp_chat_notify(SMTP_STATE *state) notice = post_mail_fopen_nowait(mail_addr_double_bounce(), var_error_rcpt, - NULL_CLEANUP_FLAGS, "NOTICE"); + NULL_CLEANUP_FLAGS); if (notice == 0) { msg_warn("postmaster notify: %m"); return; diff --git a/gnu/dist/postfix/src/smtp/smtp_proto.c b/gnu/dist/postfix/src/smtp/smtp_proto.c index a7871312379..04e0db3fcde 100644 --- a/gnu/dist/postfix/src/smtp/smtp_proto.c +++ b/gnu/dist/postfix/src/smtp/smtp_proto.c @@ -82,6 +82,7 @@ #include <vstring_vstream.h> #include <stringops.h> #include <mymalloc.h> +#include <iostuff.h> /* Global library. */ @@ -174,6 +175,14 @@ int smtp_helo(SMTP_STATE *state) session->namaddr, translit(resp->str, "\n", " "))); /* + * XXX Some PIX firewall versions require flush before ".<CR><LF>" so it + * does not span a packet boundary. This hurts performance so it is not + * on by default. + */ + if (resp->str[strspn(resp->str, "20 *\t\n")] == 0) + state->features |= SMTP_FEATURE_MAYBEPIX; + + /* * See if we are talking to ourself. This should not be possible with the * way we implement DNS lookups. However, people are known to sometimes * screw up the naming service. And, mailer loops are still possible when @@ -188,9 +197,9 @@ int smtp_helo(SMTP_STATE *state) } else if (strcasecmp(word, "ESMTP") == 0) state->features |= SMTP_FEATURE_ESMTP; } - if (var_smtp_always_ehlo) + if (var_smtp_always_ehlo && (state->features & SMTP_FEATURE_MAYBEPIX) == 0) state->features |= SMTP_FEATURE_ESMTP; - if (var_smtp_never_ehlo) + if (var_smtp_never_ehlo || (state->features & SMTP_FEATURE_MAYBEPIX) != 0) state->features &= ~SMTP_FEATURE_ESMTP; /* @@ -217,21 +226,30 @@ int smtp_helo(SMTP_STATE *state) * overflow detection, ignore the message size limit advertised by the * SMTP server. Otherwise, we might do the wrong thing when the server * advertises a really huge message size limit. + * + * XXX Allow for "code (SP|-) ehlo-keyword (SP|=) ehlo-param...", because + * MicroSoft implemented AUTH based on an old draft. */ lines = resp->str; while ((words = mystrtok(&lines, "\n")) != 0) { - if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t")) != 0) { + if (mystrtok(&words, "- ") && (word = mystrtok(&words, " \t=")) != 0) { if (strcasecmp(word, "8BITMIME") == 0) state->features |= SMTP_FEATURE_8BITMIME; else if (strcasecmp(word, "PIPELINING") == 0) state->features |= SMTP_FEATURE_PIPELINING; - else if (strcasecmp(word, "SIZE") == 0) + else if (strcasecmp(word, "SIZE") == 0) { state->features |= SMTP_FEATURE_SIZE; + if ((word = mystrtok(&words, " \t")) != 0) { + if (!alldig(word)) + msg_warn("bad size limit \"%s\" in EHLO reply from %s", + word, session->namaddr); + else + state->size_limit = off_cvt_string(word); + } + } #ifdef USE_SASL_AUTH else if (var_smtp_sasl_enable && strcasecmp(word, "AUTH") == 0) smtp_sasl_helo_auth(state, words); - else if (var_smtp_sasl_enable && strncasecmp(word, "AUTH=", 5) == 0) - smtp_sasl_helo_auth(state, word + 5); #endif else if (strcasecmp(word, var_myhostname) == 0) { msg_warn("host %s replied to HELO/EHLO with my own hostname %s", @@ -243,7 +261,8 @@ int smtp_helo(SMTP_STATE *state) } } if (msg_verbose) - msg_info("server features: 0x%x", state->features); + msg_info("server features: 0x%x size %.0f", + state->features, (double) state->size_limit); #ifdef USE_SASL_AUTH if (var_smtp_sasl_enable && (state->features & SMTP_FEATURE_AUTH)) @@ -302,6 +321,19 @@ int smtp_xfer(SMTP_STATE *state) (recv_state <= SMTP_STATE_DOT) /* + * See if we should even try to send this message at all. This code sits + * here rather than in the EHLO processing code, because of future SMTP + * connection caching. + */ + if (state->size_limit > 0 && state->size_limit < request->data_size) { + smtp_mesg_fail(state, 552, + "message size %lu exceeds size limit %.0f of server %s", + request->data_size, (double) state->size_limit, + session->namaddr); + RETURN(0); + } + + /* * We use SMTP command pipelining if the server said it supported it. * Since we use blocking I/O, RFC 2197 says that we should inspect the * TCP window size and not send more than this amount of information. @@ -487,9 +519,19 @@ int smtp_xfer(SMTP_STATE *state) * rejected, ignore RCPT TO responses: all recipients are * dead already. When all recipients are rejected the * receiver may apply a course correction. + * + * XXX 2821: Section 4.5.3.1 says that a 552 RCPT TO reply + * must be treated as if the server replied with 452. + * However, this causes "too much mail data" to be + * treated as a recoverable error, which is wrong. I'll + * stick with RFC 821. */ case SMTP_STATE_RCPT: if (!mail_from_rejected) { +#ifdef notdef + if (resp->code == 552) + resp->code = 452; +#endif if (resp->code / 100 == 2) { ++nrcpt; } else { @@ -623,6 +665,8 @@ int smtp_xfer(SMTP_STATE *state) if (prev_type != REC_TYPE_CONT) if (vstring_str(state->scratch)[0] == '.') smtp_fputc('.', session->stream); + if (var_smtp_break_lines) + rec_type = REC_TYPE_NORM; if (rec_type == REC_TYPE_CONT) smtp_fwrite(vstring_str(state->scratch), VSTRING_LEN(state->scratch), @@ -636,6 +680,14 @@ int smtp_xfer(SMTP_STATE *state) if (prev_type == REC_TYPE_CONT) /* missing newline at end */ smtp_fputs("", 0, session->stream); + if ((state->features & SMTP_FEATURE_MAYBEPIX) != 0 + && request->arrival_time < vstream_ftime(session->stream) + - var_smtp_pix_thresh) { + msg_info("%s: enabling PIX <CRLF>.<CRLF> workaround for %s", + request->queue_id, session->namaddr); + vstream_fflush(session->stream);/* hurts performance */ + sleep(var_smtp_pix_delay); /* not to mention this */ + } if (vstream_ferror(state->src)) msg_fatal("queue file read error"); if (rec_type != REC_TYPE_XTRA) diff --git a/gnu/dist/postfix/src/smtp/smtp_sasl_glue.c b/gnu/dist/postfix/src/smtp/smtp_sasl_glue.c index 6336e2e5e48..0a4e28fe313 100644 --- a/gnu/dist/postfix/src/smtp/smtp_sasl_glue.c +++ b/gnu/dist/postfix/src/smtp/smtp_sasl_glue.c @@ -139,11 +139,11 @@ static int smtp_sasl_log(void *unused_context, int priority, switch (priority) { case SASL_LOG_ERR: case SASL_LOG_WARNING: - msg_warn("%s", message); + msg_warn("SASL authentication problem: %s", message); break; case SASL_LOG_INFO: if (msg_verbose) - msg_info("%s", message); + msg_info("SASL authentication info: %s", message); break; } return (SASL_OK); diff --git a/gnu/dist/postfix/src/smtp/smtp_sasl_proto.c b/gnu/dist/postfix/src/smtp/smtp_sasl_proto.c index f3818236520..97c2592c02e 100644 --- a/gnu/dist/postfix/src/smtp/smtp_sasl_proto.c +++ b/gnu/dist/postfix/src/smtp/smtp_sasl_proto.c @@ -52,6 +52,10 @@ /* System library. */ #include <sys_defs.h> +#include <string.h> +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif /* Utility library. */ @@ -79,6 +83,8 @@ void smtp_sasl_helo_auth(SMTP_STATE *state, const char *words) * then pretend that the server doesn't support SASL authentication. */ if (state->sasl_mechanism_list) { + if (strcasecmp(state->sasl_mechanism_list, words) == 0) + return; myfree(state->sasl_mechanism_list); msg_warn("%s offered AUTH option multiple times", state->session->namaddr); diff --git a/gnu/dist/postfix/src/smtp/smtp_state.c b/gnu/dist/postfix/src/smtp/smtp_state.c index 790bdfa6bca..2c4cad35c49 100644 --- a/gnu/dist/postfix/src/smtp/smtp_state.c +++ b/gnu/dist/postfix/src/smtp/smtp_state.c @@ -68,6 +68,7 @@ SMTP_STATE *smtp_state_alloc(void) #ifdef USE_SASL_AUTH smtp_sasl_connect(state); #endif + state->size_limit = 0; return (state); } diff --git a/gnu/dist/postfix/src/smtpd/Makefile.in b/gnu/dist/postfix/src/smtpd/Makefile.in index aebfcaeac19..8668041980e 100644 --- a/gnu/dist/postfix/src/smtpd/Makefile.in +++ b/gnu/dist/postfix/src/smtpd/Makefile.in @@ -70,19 +70,19 @@ depend: $(MAKES) tests: smtpd_check_test smtpd_check_test2 smtpd_acl_test smtpd_token_test smtpd_check_test: smtpd_check smtpd_check.in smtpd_check.ref - ../postmap/postmap smtpd_check_access + ../postmap/postmap hash:smtpd_check_access ./smtpd_check <smtpd_check.in >smtpd_check.tmp 2>&1 diff smtpd_check.ref smtpd_check.tmp rm -f smtpd_check.tmp smtpd_check_access.* smtpd_check_test2: smtpd_check smtpd_check.in2 smtpd_check.ref2 - ../postmap/postmap smtpd_check_access + ../postmap/postmap hash:smtpd_check_access ./smtpd_check <smtpd_check.in2 >smtpd_check.tmp 2>&1 diff smtpd_check.ref2 smtpd_check.tmp rm -f smtpd_check.tmp smtpd_check_access.* smtpd_acl_test: smtpd_check smtpd_acl.in smtpd_acl.ref - ../postmap/postmap smtpd_check_access + ../postmap/postmap hash:smtpd_check_access ./smtpd_check <smtpd_acl.in >smtpd_check.tmp 2>&1 diff smtpd_acl.ref smtpd_check.tmp rm -f smtpd_check.tmp smtpd_check_access.* @@ -113,6 +113,7 @@ smtpd.o: ../../include/record.h smtpd.o: ../../include/rec_type.h smtpd.o: ../../include/mail_proto.h smtpd.o: ../../include/iostuff.h +smtpd.o: ../../include/attr.h smtpd.o: ../../include/cleanup_user.h smtpd.o: ../../include/mail_date.h smtpd.o: ../../include/mail_conf.h @@ -125,6 +126,10 @@ smtpd.o: ../../include/mail_stream.h smtpd.o: ../../include/mail_queue.h smtpd.o: ../../include/tok822.h smtpd.o: ../../include/resolve_clnt.h +smtpd.o: ../../include/verp_sender.h +smtpd.o: ../../include/string_list.h +smtpd.o: ../../include/match_list.h +smtpd.o: ../../include/match_ops.h smtpd.o: ../../include/mail_server.h smtpd.o: smtpd_token.h smtpd.o: smtpd.h @@ -147,6 +152,7 @@ smtpd_chat.o: ../../include/record.h smtpd_chat.o: ../../include/rec_type.h smtpd_chat.o: ../../include/mail_proto.h smtpd_chat.o: ../../include/iostuff.h +smtpd_chat.o: ../../include/attr.h smtpd_chat.o: ../../include/mail_params.h smtpd_chat.o: ../../include/mail_addr.h smtpd_chat.o: ../../include/post_mail.h @@ -173,6 +179,8 @@ smtpd_check.o: ../../include/htable.h smtpd_check.o: ../../include/ctable.h smtpd_check.o: ../../include/dns.h smtpd_check.o: ../../include/namadr_list.h +smtpd_check.o: ../../include/match_list.h +smtpd_check.o: ../../include/match_ops.h smtpd_check.o: ../../include/domain_list.h smtpd_check.o: ../../include/mail_params.h smtpd_check.o: ../../include/canon_addr.h @@ -184,6 +192,8 @@ smtpd_check.o: ../../include/own_inet_addr.h smtpd_check.o: ../../include/mail_conf.h smtpd_check.o: ../../include/maps.h smtpd_check.o: ../../include/mail_addr_find.h +smtpd_check.o: ../../include/match_parent_style.h +smtpd_check.o: ../../include/split_addr.h smtpd_check.o: smtpd.h smtpd_check.o: ../../include/mail_stream.h smtpd_check.o: smtpd_sasl_glue.h @@ -205,6 +215,8 @@ smtpd_sasl_glue.o: ../../include/sys_defs.h smtpd_sasl_glue.o: ../../include/msg.h smtpd_sasl_glue.o: ../../include/mymalloc.h smtpd_sasl_glue.o: ../../include/namadr_list.h +smtpd_sasl_glue.o: ../../include/match_list.h +smtpd_sasl_glue.o: ../../include/match_ops.h smtpd_sasl_glue.o: ../../include/name_mask.h smtpd_sasl_glue.o: ../../include/mail_params.h smtpd_sasl_glue.o: ../../include/smtp_stream.h @@ -225,6 +237,7 @@ smtpd_sasl_proto.o: ../../include/mail_proto.h smtpd_sasl_proto.o: ../../include/vstream.h smtpd_sasl_proto.o: ../../include/vbuf.h smtpd_sasl_proto.o: ../../include/iostuff.h +smtpd_sasl_proto.o: ../../include/attr.h smtpd_sasl_proto.o: ../../include/mail_error.h smtpd_sasl_proto.o: ../../include/name_mask.h smtpd_sasl_proto.o: smtpd.h diff --git a/gnu/dist/postfix/src/smtpd/smtpd.h b/gnu/dist/postfix/src/smtpd/smtpd.h index 29fb3d2d743..20386ad73fc 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd.h +++ b/gnu/dist/postfix/src/smtpd/smtpd.h @@ -76,6 +76,7 @@ typedef struct SMTPD_STATE { VSTRING *sasl_encoded; VSTRING *sasl_decoded; #endif + int warn_if_reject; } SMTPD_STATE; extern void smtpd_state_init(SMTPD_STATE *, VSTREAM *); diff --git a/gnu/dist/postfix/src/smtpd/smtpd_acl.in b/gnu/dist/postfix/src/smtpd/smtpd_acl.in index e633c48d561..99955409f73 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_acl.in +++ b/gnu/dist/postfix/src/smtpd/smtpd_acl.in @@ -4,6 +4,7 @@ smtpd_delay_reject 0 mynetworks 127.0.0.0/8,168.100.189.0/28 relay_domains porcupine.org +smtpd_null_access_lookup_key <> # # Test check_domain_access() # @@ -105,3 +106,7 @@ rcpt ok@ok.domain rcpt anyone@ok.domain # Expect: OK rcpt bad-sender@ok.domain +# +# check_sender_access specific +# +mail <> diff --git a/gnu/dist/postfix/src/smtpd/smtpd_acl.ref b/gnu/dist/postfix/src/smtpd/smtpd_acl.ref index 34e35186627..294891fd0c4 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_acl.ref +++ b/gnu/dist/postfix/src/smtpd/smtpd_acl.ref @@ -7,6 +7,8 @@ OK OK >>> relay_domains porcupine.org OK +>>> smtpd_null_access_lookup_key <> +OK >>> # >>> # Test check_domain_access() >>> # @@ -162,3 +164,9 @@ OK >>> # Expect: OK >>> rcpt bad-sender@ok.domain OK +>>> # +>>> # check_sender_access specific +>>> # +>>> mail <> +./smtpd_check: reject: MAIL from bar.duno.com[44.33.44.33]: 550 <>: Sender address rejected: Go away postmaster; from=<> +550 <>: Sender address rejected: Go away postmaster diff --git a/gnu/dist/postfix/src/smtpd/smtpd_chat.c b/gnu/dist/postfix/src/smtpd/smtpd_chat.c index 2d1f8772672..b9be60015cd 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_chat.c +++ b/gnu/dist/postfix/src/smtpd/smtpd_chat.c @@ -107,6 +107,9 @@ static void smtp_chat_append(SMTPD_STATE *state, char *direction) { char *line; + if (state->notify_mask == 0) + return; + if (state->history == 0) state->history = argv_alloc(10); line = concatenate(direction, STR(state->buffer), (char *) 0); @@ -207,7 +210,7 @@ void smtpd_chat_notify(SMTPD_STATE *state) notice = post_mail_fopen_nowait(mail_addr_double_bounce(), var_error_rcpt, - NULL_CLEANUP_FLAGS, "NOTICE"); + NULL_CLEANUP_FLAGS); if (notice == 0) { msg_warn("postmaster notify: %m"); return; @@ -227,7 +230,5 @@ void smtpd_chat_notify(SMTPD_STATE *state) post_mail_fputs(notice, ""); if (state->reason) post_mail_fprintf(notice, "Session aborted, reason: %s", state->reason); - else - post_mail_fputs(notice, "No message was collected successfully."); (void) post_mail_fclose(notice); } diff --git a/gnu/dist/postfix/src/smtpd/smtpd_check.in b/gnu/dist/postfix/src/smtpd/smtpd_check.in index f7c3cdd709d..44f969ce9c6 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_check.in +++ b/gnu/dist/postfix/src/smtpd/smtpd_check.in @@ -6,6 +6,7 @@ smtpd_delay_reject 0 mynetworks 127.0.0.0/8,168.100.189.0/28 relay_domains porcupine.org +maps_rbl_domains blackholes.mail-abuse.org # # Test the client restrictions. # diff --git a/gnu/dist/postfix/src/smtpd/smtpd_check.in2 b/gnu/dist/postfix/src/smtpd/smtpd_check.in2 index 0eaf8c02b9e..2b9000267b4 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_check.in2 +++ b/gnu/dist/postfix/src/smtpd/smtpd_check.in2 @@ -6,6 +6,7 @@ smtpd_delay_reject 0 mynetworks 127.0.0.0/8,168.100.189.0/28 relay_domains porcupine.org +maps_rbl_domains blackholes.mail-abuse.org # # Test the client restrictions. # diff --git a/gnu/dist/postfix/src/smtpd/smtpd_check.ref b/gnu/dist/postfix/src/smtpd/smtpd_check.ref index b63c3f70545..121c9e32a5d 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_check.ref +++ b/gnu/dist/postfix/src/smtpd/smtpd_check.ref @@ -9,6 +9,8 @@ OK OK >>> relay_domains porcupine.org OK +>>> maps_rbl_domains blackholes.mail-abuse.org +OK >>> # >>> # Test the client restrictions. >>> # diff --git a/gnu/dist/postfix/src/smtpd/smtpd_check.ref2 b/gnu/dist/postfix/src/smtpd/smtpd_check.ref2 index c8d014f6000..2bd38266375 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_check.ref2 +++ b/gnu/dist/postfix/src/smtpd/smtpd_check.ref2 @@ -9,6 +9,8 @@ OK OK >>> relay_domains porcupine.org OK +>>> maps_rbl_domains blackholes.mail-abuse.org +OK >>> # >>> # Test the client restrictions. >>> # diff --git a/gnu/dist/postfix/src/smtpd/smtpd_check_access b/gnu/dist/postfix/src/smtpd/smtpd_check_access index afb5487c965..b9cf1761fa0 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_check_access +++ b/gnu/dist/postfix/src/smtpd/smtpd_check_access @@ -29,3 +29,4 @@ reject.domain REJECT reject@ok.domain REJECT ok@ok.domain OK ok.domain OK +<> 550 Go away postmaster diff --git a/gnu/dist/postfix/src/smtpd/smtpd_sasl_glue.c b/gnu/dist/postfix/src/smtpd/smtpd_sasl_glue.c index caa3ec04fb1..716b7873dd3 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_sasl_glue.c +++ b/gnu/dist/postfix/src/smtpd/smtpd_sasl_glue.c @@ -118,11 +118,11 @@ static int smtpd_sasl_log(void *unused_context, int priority, switch (priority) { case SASL_LOG_ERR: case SASL_LOG_WARNING: - msg_warn("%s", message); + msg_warn("SASL authentication problem: %s", message); break; case SASL_LOG_INFO: if (msg_verbose) - msg_info("%s", message); + msg_info("SASL authentication info: %s", message); break; } return SASL_OK; diff --git a/gnu/dist/postfix/src/smtpd/smtpd_state.c b/gnu/dist/postfix/src/smtpd/smtpd_state.c index ca492963487..6e7236543e9 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_state.c +++ b/gnu/dist/postfix/src/smtpd/smtpd_state.c @@ -91,6 +91,7 @@ void smtpd_state_init(SMTPD_STATE *state, VSTREAM *stream) state->recursion = 0; state->msg_size = 0; state->junk_cmds = 0; + state->warn_if_reject = 0; #ifdef USE_SASL_AUTH if (SMTPD_STAND_ALONE(state)) diff --git a/gnu/dist/postfix/src/smtpd/smtpd_token.c b/gnu/dist/postfix/src/smtpd/smtpd_token.c index 09d64cd01e5..66bc6bc9c38 100644 --- a/gnu/dist/postfix/src/smtpd/smtpd_token.c +++ b/gnu/dist/postfix/src/smtpd/smtpd_token.c @@ -196,7 +196,7 @@ int smtpd_token(char *cp, SMTPD_TOKEN **argvp) #include <vstream.h> #include <vstring_vstream.h> -main(int unused_argc, char **unused_argv) +int main(int unused_argc, char **unused_argv) { VSTRING *vp = vstring_alloc(10); int tok_argc; diff --git a/gnu/dist/postfix/src/smtpstone/Makefile.in b/gnu/dist/postfix/src/smtpstone/Makefile.in index 5cf7920e0e6..454a44c1343 100644 --- a/gnu/dist/postfix/src/smtpstone/Makefile.in +++ b/gnu/dist/postfix/src/smtpstone/Makefile.in @@ -1,6 +1,6 @@ SHELL = /bin/sh -SRCS = smtp-source.c smtp-sink.c -OBJS = smtp-source.o smtp-sink.o +SRCS = smtp-source.c smtp-sink.c qmqp-source.c qmqp-sink.c +OBJS = smtp-source.o smtp-sink.o qmqp-source.o qmqp-sink.o HDRS = TESTSRC = WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ @@ -10,7 +10,7 @@ DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) CFLAGS = $(DEBUG) $(OPT) $(DEFS) TESTPROG= INC_DIR = ../../include -PROG = smtp-source smtp-sink +PROG = smtp-source smtp-sink qmqp-source qmqp-sink LIBS = ../../lib/libglobal.a ../../lib/libutil.a .c.o:; $(CC) $(CFLAGS) -c $*.c @@ -26,9 +26,15 @@ smtp-sink: smtp-sink.o $(LIBS) smtp-source: smtp-source.o $(LIBS) $(CC) $(CFLAGS) -o $@ smtp-source.o $(LIBS) $(SYSLIBS) +qmqp-sink: qmqp-sink.o $(LIBS) + $(CC) $(CFLAGS) -o $@ qmqp-sink.o $(LIBS) $(SYSLIBS) + +qmqp-source: qmqp-source.o $(LIBS) + $(CC) $(CFLAGS) -o $@ qmqp-source.o $(LIBS) $(SYSLIBS) + test: $(TESTPROG) -update: ../../bin/smtp-source ../../bin/smtp-sink +update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/qmqp-source ../../bin/smtp-source: smtp-source cp $? $@ @@ -36,6 +42,12 @@ update: ../../bin/smtp-source ../../bin/smtp-sink ../../bin/smtp-sink: smtp-sink cp $? $@ +../../bin/qmqp-source: qmqp-source + cp $? $@ + +../../bin/qmqp-sink: qmqp-sink + cp $? $@ + printfck: $(OBJS) $(PROG) rm -rf printfck mkdir printfck @@ -61,6 +73,36 @@ depend: $(MAKES) @$(EXPORT) make -f Makefile.in Makefile 1>&2 # do not edit below this line - it is generated by 'make depend' +qmqp-sink.o: qmqp-sink.c +qmqp-sink.o: ../../include/sys_defs.h +qmqp-sink.o: ../../include/msg.h +qmqp-sink.o: ../../include/vstring.h +qmqp-sink.o: ../../include/vbuf.h +qmqp-sink.o: ../../include/vstream.h +qmqp-sink.o: ../../include/listen.h +qmqp-sink.o: ../../include/iostuff.h +qmqp-sink.o: ../../include/events.h +qmqp-sink.o: ../../include/mymalloc.h +qmqp-sink.o: ../../include/msg_vstream.h +qmqp-sink.o: ../../include/netstring.h +qmqp-sink.o: ../../include/qmqp_proto.h +qmqp-source.o: qmqp-source.c +qmqp-source.o: ../../include/sys_defs.h +qmqp-source.o: ../../include/msg.h +qmqp-source.o: ../../include/msg_vstream.h +qmqp-source.o: ../../include/vstream.h +qmqp-source.o: ../../include/vbuf.h +qmqp-source.o: ../../include/vstring.h +qmqp-source.o: ../../include/get_hostname.h +qmqp-source.o: ../../include/split_at.h +qmqp-source.o: ../../include/connect.h +qmqp-source.o: ../../include/iostuff.h +qmqp-source.o: ../../include/mymalloc.h +qmqp-source.o: ../../include/events.h +qmqp-source.o: ../../include/find_inet.h +qmqp-source.o: ../../include/netstring.h +qmqp-source.o: ../../include/mail_date.h +qmqp-source.o: ../../include/qmqp_proto.h smtp-sink.o: smtp-sink.c smtp-sink.o: ../../include/sys_defs.h smtp-sink.o: ../../include/msg.h diff --git a/gnu/dist/postfix/src/smtpstone/qmqp-sink.c b/gnu/dist/postfix/src/smtpstone/qmqp-sink.c new file mode 100644 index 00000000000..1c696b8991b --- /dev/null +++ b/gnu/dist/postfix/src/smtpstone/qmqp-sink.c @@ -0,0 +1,287 @@ +/*++ +/* NAME +/* qmqp-sink 8 +/* SUMMARY +/* multi-threaded QMQP test server +/* SYNOPSIS +/* .fi +/* \fBqmqp-sink\fR [\fB-cv\fR] [\fB-x \fItime\fR] +/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR +/* +/* \fBqmqp-sink\fR [\fB-cv\fR] [\fB-x \fItime\fR] +/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* DESCRIPTION +/* \fIqmqp-sink\fR listens on the named host (or address) and port. +/* It receives messages from the network and throws them away. +/* The purpose is to measure QMQP client performance, not protocol +/* compliance. +/* Connections can be accepted on IPV4 endpoints or UNIX-domain sockets. +/* IPV4 is the default. +/* This program is the complement of the \fIqmqp-source\fR program. +/* .IP \fB-c\fR +/* Display a running counter that is updated whenever a delivery +/* is completed. +/* .IP \fB-v\fR +/* Increase verbosity. Specify \fB-v -v\fR to see some of the QMQP +/* conversation. +/* .IP "\fB-x \fItime\fR +/* Terminate after \fItime\fR seconds. This is to facilitate memory +/* leak testing. +/* SEE ALSO +/* qmqp-source, QMQP test message generator +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <fcntl.h> + +#ifdef STRCASECMP_IN_STRINGS_H +#include <strings.h> +#endif + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <listen.h> +#include <events.h> +#include <mymalloc.h> +#include <iostuff.h> +#include <msg_vstream.h> +#include <netstring.h> + +/* Global library. */ + +#include <qmqp_proto.h> + +/* Application-specific. */ + +typedef struct { + VSTREAM *stream; /* client connection */ + int count; /* bytes to go */ +} SINK_STATE; + +static int var_tmout; +static VSTRING *buffer; +static void disconnect(SINK_STATE *); +static int count; +static int counter; + +/* send_reply - finish conversation */ + +static void send_reply(SINK_STATE *state) +{ + vstring_sprintf(buffer, "%cOk", QMQP_STAT_OK); + NETSTRING_PUT_BUF(state->stream, buffer); + netstring_fflush(state->stream); + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + disconnect(state); +} + +/* read_data - read over-all netstring data */ + +static void read_data(int unused_event, char *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + int fd = vstream_fileno(state->stream); + int count; + + /* + * Refill the VSTREAM buffer, if necessary. + */ + if (VSTREAM_GETC(state->stream) == VSTREAM_EOF) + netstring_except(state->stream, vstream_ftimeout(state->stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + state->count--; + + /* + * Flush the VSTREAM buffer. As documented, vstream_fseek() discards + * unread input. + */ + if ((count = vstream_peek(state->stream)) > 0) { + state->count -= count; + if (state->count <= 0) { + send_reply(state); + return; + } + vstream_fseek(state->stream, 0L, 0); + } + + /* + * Do not block while waiting for the arrival of more data. + */ + event_disable_readwrite(fd); + event_enable_read(fd, read_data, context); +} + +/* read_length - read over-all netstring length */ + +static void read_length(int event, char *context) +{ + SINK_STATE *state = (SINK_STATE *) context; + + switch (vstream_setjmp(state->stream)) { + + default: + msg_panic("unknown error reading input"); + + case NETSTRING_ERR_TIME: + msg_panic("attempt to read non-readable socket"); + /* NOTREACHED */ + + case NETSTRING_ERR_EOF: + msg_warn("lost connection"); + disconnect(state); + return; + + case NETSTRING_ERR_FORMAT: + msg_warn("netstring format error"); + disconnect(state); + return; + + case NETSTRING_ERR_SIZE: + msg_warn("netstring size error"); + disconnect(state); + return; + + /* + * Include the netstring terminator in the read byte count. This + * violates abstractions. + */ + case 0: + state->count = netstring_get_length(state->stream) + 1; + read_data(event, context); + return; + } +} + +/* disconnect - handle disconnection events */ + +static void disconnect(SINK_STATE *state) +{ + event_disable_readwrite(vstream_fileno(state->stream)); + vstream_fclose(state->stream); + myfree((char *) state); +} + +/* connect_event - handle connection events */ + +static void connect_event(int unused_event, char *context) +{ + int sock = CAST_CHAR_PTR_TO_INT(context); + struct sockaddr sa; + SOCKADDR_SIZE len = sizeof(sa); + SINK_STATE *state; + int fd; + + if ((fd = accept(sock, &sa, &len)) >= 0) { + if (msg_verbose) + msg_info("connect (%s)", +#ifdef AF_LOCAL + sa.sa_family == AF_LOCAL ? "AF_LOCAL" : +#else + sa.sa_family == AF_UNIX ? "AF_UNIX" : +#endif + sa.sa_family == AF_INET ? "AF_INET" : +#ifdef AF_INET6 + sa.sa_family == AF_INET6 ? "AF_INET6" : +#endif + "unknown protocol family"); + non_blocking(fd, NON_BLOCKING); + state = (SINK_STATE *) mymalloc(sizeof(*state)); + state->stream = vstream_fdopen(fd, O_RDWR); + netstring_setup(state->stream, var_tmout); + event_enable_read(fd, read_length, (char *) state); + } +} + +/* terminate - voluntary exit */ + +static void terminate(int unused_event, char *unused_context) +{ + exit(0); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s [-cv] [-x time] [host]:port backlog", myname); +} + +int main(int argc, char **argv) +{ + int sock; + int backlog; + int ch; + int ttl; + + /* + * Initialize diagnostics. + */ + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "cvx:")) > 0) { + switch (ch) { + case 'c': + count++; + break; + case 'v': + msg_verbose++; + break; + case 'x': + if ((ttl = atoi(optarg)) <= 0) + usage(argv[0]); + event_request_timer(terminate, (char *) 0, ttl); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 2) + usage(argv[0]); + if ((backlog = atoi(argv[optind + 1])) <= 0) + usage(argv[0]); + + /* + * Initialize. + */ + buffer = vstring_alloc(1024); + if (strncmp(argv[optind], "unix:", 5) == 0) { + sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + sock = inet_listen(argv[optind], backlog, BLOCKING); + } + + /* + * Start the event handler. + */ + event_enable_read(sock, connect_event, CAST_INT_TO_CHAR_PTR(sock)); + for (;;) + event_loop(-1); +} diff --git a/gnu/dist/postfix/src/smtpstone/qmqp-source.c b/gnu/dist/postfix/src/smtpstone/qmqp-source.c new file mode 100644 index 00000000000..66fd33854e3 --- /dev/null +++ b/gnu/dist/postfix/src/smtpstone/qmqp-source.c @@ -0,0 +1,593 @@ +/*++ +/* NAME +/* qmqp-source 8 +/* SUMMARY +/* multi-threaded QMQP test generator +/* SYNOPSIS +/* .fi +/* \fBqmqp-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* +/* \fBqmqp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR +/* DESCRIPTION +/* qmqp-source connects to the named host and TCP port (default 628) +/* and sends one or more messages to it, either sequentially +/* or in parallel. The program speaks the QMQP protocol. +/* Connections can be made to UNIX-domain and IPV4 servers. +/* IPV4 is the default. +/* +/* Options: +/* .IP -c +/* Display a running counter that is incremented each time +/* a delivery completes. +/* .IP "-C count" +/* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times +/* before giving up. The default count is 1. Specify a larger count in +/* order to work around a problem with TCP/IP stacks that send RESET +/* when the listen queue is full. +/* .IP "-f from" +/* Use the specified sender address (default: <foo@myhostname>). +/* .IP "-l length" +/* Send \fIlength\fR bytes as message payload. The length +/* includes the message headers. +/* .IP "-m message_count" +/* Send the specified number of messages (default: 1). +/* .IP "-r recipient_count" +/* Send the specified number of recipients per transaction (default: 1). +/* Recipient names are generated by prepending a number to the +/* recipient address. +/* .IP "-s session_count" +/* Run the specified number of QMQP sessions in parallel (default: 1). +/* .IP "-t to" +/* Use the specified recipient address (default: <foo@myhostname>). +/* .IP "-R interval" +/* Wait for a random period of time 0 <= n <= interval between messages. +/* Suspending one thread does not affect other delivery threads. +/* .IP "-w interval" +/* Wait a fixed time between messages. +/* Suspending one thread does not affect other delivery threads. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/socket.h> +#include <sys/wait.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <msg_vstream.h> +#include <vstring.h> +#include <vstream.h> +#include <get_hostname.h> +#include <split_at.h> +#include <connect.h> +#include <mymalloc.h> +#include <events.h> +#include <find_inet.h> +#include <iostuff.h> +#include <netstring.h> + +/* Global library. */ + +#include <mail_date.h> +#include <qmqp_proto.h> + +/* Application-specific. */ + + /* + * Per-session data structure with state. + * + * This software can maintain multiple parallel connections to the same QMQP + * server. However, it makes no more than one connection request at a time + * to avoid overwhelming the server with SYN packets and having to back off. + * Back-off would screw up the benchmark. Pending connection requests are + * kept in a linear list. + */ +typedef struct SESSION { + int xfer_count; /* # of xfers in session */ + int rcpt_done; /* # of recipients done */ + int rcpt_count; /* # of recipients to go */ + VSTREAM *stream; /* open connection */ + int connect_count; /* # of connect()s to retry */ + struct SESSION *next; /* connect() queue linkage */ +} SESSION; + +static SESSION *last_session; /* connect() queue tail */ + +static VSTRING *buffer; +static int var_line_limit = 10240; +static int var_timeout = 300; +static const char *var_myhostname; +static int session_count; +static int message_count = 1; +static struct sockaddr_in sin; + +#undef sun +static struct sockaddr_un sun; +static struct sockaddr *sa; +static int sa_length; +static int recipients = 1; +static char *defaddr; +static char *recipient; +static char *sender; +static int message_length = 1024; +static int count = 0; +static int counter = 0; +static int connect_count = 1; +static int random_delay = 0; +static int fixed_delay = 0; +static const char *mydate; +static int mypid; + +static void enqueue_connect(SESSION *); +static void start_connect(SESSION *); +static void connect_done(int, char *); + +static void send_data(SESSION *); +static void receive_reply(int, char *); + +static VSTRING *message_buffer; +static VSTRING *sender_buffer; +static VSTRING *recipient_buffer; + +/* Silly little macros. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* random_interval - generate a random value in 0 .. (small) interval */ + +static int random_interval(int interval) +{ + return (rand() % (interval + 1)); +} + +/* socket_error - look up and reset the last socket error */ + +static int socket_error(int sock) +{ + int error; + SOCKOPT_SIZE error_len; + + /* + * Some Solaris 2 versions have getsockopt() itself return the error, + * instead of returning it via the parameter list. + */ + error = 0; + error_len = sizeof(error); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, (char *) &error, &error_len) < 0) + return (-1); + if (error) { + errno = error; + return (-1); + } + + /* + * No problems. + */ + return (0); +} + +/* exception_text - translate exceptions from the netstring module */ + +static char *exception_text(int except) +{ + ; + + switch (except) { + case NETSTRING_ERR_EOF: + return ("lost connection"); + case NETSTRING_ERR_TIME: + return ("timeout"); + case NETSTRING_ERR_FORMAT: + return ("netstring format error"); + case NETSTRING_ERR_SIZE: + return ("netstring size exceeds limit"); + default: + msg_panic("exception_text: unknown exception %d", except); + } + /* NOTREACHED */ +} + +/* startup - connect to server but do not wait */ + +static void startup(SESSION *session) +{ + if (message_count-- <= 0) { + myfree((char *) session); + session_count--; + return; + } + enqueue_connect(session); +} + +/* start_event - invoke startup from timer context */ + +static void start_event(int unused_event, char *context) +{ + SESSION *session = (SESSION *) context; + + startup(session); +} + +/* start_another - start another session */ + +static void start_another(SESSION *session) +{ + if (random_delay > 0) { + event_request_timer(start_event, (char *) session, + random_interval(random_delay)); + } else if (fixed_delay > 0) { + event_request_timer(start_event, (char *) session, fixed_delay); + } else { + startup(session); + } +} + +/* enqueue_connect - queue a connection request */ + +static void enqueue_connect(SESSION *session) +{ + session->next = 0; + if (last_session == 0) { + last_session = session; + start_connect(session); + } else { + last_session->next = session; + last_session = session; + } +} + +/* dequeue_connect - connection request completed */ + +static void dequeue_connect(SESSION *session) +{ + if (session == last_session) { + if (session->next != 0) + msg_panic("dequeue_connect: queue ends after last"); + last_session = 0; + } else { + if (session->next == 0) + msg_panic("dequeue_connect: queue ends before last"); + start_connect(session->next); + } +} + +/* fail_connect - handle failed startup */ + +static void fail_connect(SESSION *session) +{ + if (session->connect_count-- == 1) + msg_fatal("connect: %m"); + msg_warn("connect: %m"); + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; +#ifdef MISSING_USLEEP + doze(10); +#else + usleep(10); +#endif + start_connect(session); +} + +/* start_connect - start TCP handshake */ + +static void start_connect(SESSION *session) +{ + int fd; + + /* + * Some systems don't set the socket error when connect() fails early + * (loopback) so we must deal with the error immediately, rather than + * retrieving it later with getsockopt(). We can't use MSG_PEEK to + * distinguish between server disconnect and connection refused. + */ + if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) + msg_fatal("socket: %m"); + (void) non_blocking(fd, NON_BLOCKING); + session->stream = vstream_fdopen(fd, O_RDWR); + event_enable_write(fd, connect_done, (char *) session); + netstring_setup(session->stream, var_timeout); + if (connect(fd, sa, sa_length) < 0 && errno != EINPROGRESS) + fail_connect(session); +} + +/* connect_done - send message sender info */ + +static void connect_done(int unused_event, char *context) +{ + SESSION *session = (SESSION *) context; + int fd = vstream_fileno(session->stream); + + /* + * Try again after some delay when the connection failed, in case they + * run a Mickey Mouse protocol stack. + */ + if (socket_error(fd) < 0) { + fail_connect(session); + } else { + dequeue_connect(session); + non_blocking(fd, BLOCKING); + event_disable_readwrite(fd); + send_data(session); + } +} + +/* send_data - send message+sender+recipients */ + +static void send_data(SESSION *session) +{ + int fd = vstream_fileno(session->stream); + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while sending message", exception_text(except)); + + /* + * Send the message content, by wrapping three netstrings into an + * over-all netstring. + * + * XXX This should be done more carefully to avoid blocking when sending + * large messages over slow networks. + */ + netstring_put_multi(session->stream, + STR(message_buffer), LEN(message_buffer), + STR(sender_buffer), LEN(sender_buffer), + STR(recipient_buffer), LEN(recipient_buffer), + 0); + netstring_fflush(session->stream); + + /* + * Wake me up when the server replies or when something bad happens. + */ + event_enable_read(fd, receive_reply, (char *) session); +} + +/* receive_reply - read server reply */ + +static void receive_reply(int unused_event, char *context) +{ + SESSION *session = (SESSION *) context; + int except; + + /* + * Prepare for disaster. + */ + if ((except = vstream_setjmp(session->stream)) != 0) + msg_fatal("%s while receiving server reply", exception_text(except)); + + /* + * Receive and process the server reply. + */ + netstring_get(session->stream, buffer, var_line_limit); + if (msg_verbose) + vstream_printf("<< %.*s\n", LEN(buffer), STR(buffer)); + if (STR(buffer)[0] != QMQP_STAT_OK) + msg_fatal("%s error: %.*s", + STR(buffer)[0] == QMQP_STAT_RETRY ? "recoverable" : + STR(buffer)[0] == QMQP_STAT_HARD ? "unrecoverable" : + "unknown", LEN(buffer) - 1, STR(buffer) + 1); + + /* + * Update the optional running counter. + */ + if (count) { + counter++; + vstream_printf("%d\r", counter); + vstream_fflush(VSTREAM_OUT); + } + + /* + * Finish this session. QMQP sends only one message per session. + */ + event_disable_readwrite(vstream_fileno(session->stream)); + vstream_fclose(session->stream); + session->stream = 0; + start_another(session); +} + +/* usage - explain */ + +static void usage(char *myname) +{ + msg_fatal("usage: %s -s sess -l msglen -m msgs -c -C count -f from -t to -R delay -v -w delay host[:port]", myname); +} + +/* main - parse JCL and start the machine */ + +int main(int argc, char **argv) +{ + SESSION *session; + char *host; + char *port; + char *path; + int path_len; + int sessions = 1; + int ch; + int n; + int i; + + signal(SIGPIPE, SIG_IGN); + msg_vstream_init(argv[0], VSTREAM_ERR); + + /* + * Parse JCL. + */ + while ((ch = GETOPT(argc, argv, "cC:f:l:m:r:R:s:t:vw:")) > 0) { + switch (ch) { + case 'c': + count++; + break; + case 'C': + if ((connect_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'f': + sender = optarg; + break; + case 'l': + if ((message_length = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'm': + if ((message_count = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'r': + if ((recipients = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 'R': + if (fixed_delay > 0 || (random_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 's': + if ((sessions = atoi(optarg)) <= 0) + usage(argv[0]); + break; + case 't': + recipient = optarg; + break; + case 'v': + msg_verbose++; + break; + case 'w': + if (random_delay > 0 || (fixed_delay = atoi(optarg)) <= 0) + usage(argv[0]); + break; + default: + usage(argv[0]); + } + } + if (argc - optind != 1) + usage(argv[0]); + + if (random_delay > 0) + srand(getpid()); + + /* + * Translate endpoint address to internal form. + */ + if (strncmp(argv[optind], "unix:", 5) == 0) { + path = argv[optind] + 5; + path_len = strlen(path); + if (path_len >= (int) sizeof(sun.sun_path)) + msg_fatal("unix-domain name too long: %s", path); + memset((char *) &sun, 0, sizeof(sun)); + sun.sun_family = AF_UNIX; +#ifdef HAS_SUN_LEN + sun.sun_len = path_len + 1; +#endif + memcpy(sun.sun_path, path, path_len); + sa = (struct sockaddr *) & sun; + sa_length = sizeof(sun); + } else { + if (strncmp(argv[optind], "inet:", 5) == 0) + argv[optind] += 5; + if ((port = split_at(host = argv[optind], ':')) == 0 || *port == 0) + port = "628"; + memset((char *) &sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = find_inet_addr(host); + sin.sin_port = find_inet_port(port, "tcp"); + sa = (struct sockaddr *) & sin; + sa_length = sizeof(sin); + } + + /* + * Allocate space for temporary buffer. + */ + buffer = vstring_alloc(100); + + /* + * Make sure we have sender and recipient addresses. + */ + var_myhostname = get_hostname(); + if (sender == 0 || recipient == 0) { + vstring_sprintf(buffer, "foo@%s", var_myhostname); + defaddr = mystrdup(vstring_str(buffer)); + if (sender == 0) + sender = defaddr; + if (recipient == 0) + recipient = defaddr; + } + + /* + * Prepare some results that may be used multiple times: the message + * content netstring, the sender netstring, and the recipient netstrings. + */ + mydate = mail_date(time((time_t *) 0)); + mypid = getpid(); + + message_buffer = vstring_alloc(message_length + 200); + vstring_sprintf(buffer, + "From: <%s>\nTo: <%s>\nDate: %s\nMessage-Id: <%d@%s>\n\n", + sender, recipient, mydate, mypid, var_myhostname); + for (n = 1; LEN(buffer) < message_length; n++) { + for (i = 0; i < n && i < 79; i++) + VSTRING_ADDCH(buffer, 'X'); + VSTRING_ADDCH(buffer, '\n'); + } + STR(buffer)[message_length - 1] = '\n'; + netstring_memcpy(message_buffer, STR(buffer), message_length); + + n = strlen(sender); + sender_buffer = vstring_alloc(n); + netstring_memcpy(sender_buffer, sender, n); + + if (recipients == 1) { + n = strlen(recipient); + recipient_buffer = vstring_alloc(n); + netstring_memcpy(recipient_buffer, recipient, n); + } else { + recipient_buffer = vstring_alloc(100); + for (n = 0; n < recipients; n++) { + vstring_sprintf(buffer, "%d%s", n, recipient); + netstring_memcat(recipient_buffer, STR(buffer), LEN(buffer)); + } + } + + /* + * Start sessions. + */ + while (sessions-- > 0) { + session = (SESSION *) mymalloc(sizeof(*session)); + session->stream = 0; + session->xfer_count = 0; + session->connect_count = connect_count; + session->next = 0; + session_count++; + startup(session); + } + for (;;) { + event_loop(-1); + if (session_count <= 0 && message_count <= 0) { + if (count) { + VSTREAM_PUTC('\n', VSTREAM_OUT); + vstream_fflush(VSTREAM_OUT); + } + exit(0); + } + } +} diff --git a/gnu/dist/postfix/src/smtpstone/smtp-sink.c b/gnu/dist/postfix/src/smtpstone/smtp-sink.c index 81aab916b36..24a5e278492 100644 --- a/gnu/dist/postfix/src/smtpstone/smtp-sink.c +++ b/gnu/dist/postfix/src/smtpstone/smtp-sink.c @@ -5,11 +5,10 @@ /* multi-threaded SMTP/LMTP test server /* SYNOPSIS /* .fi -/* \fBsmtp-sink\fR [\fB-cLpv\fR] [\fB-w \fIdelay\fR] -/* [\fBinet:\fR][\fIhost\fR]:\fIport\fR \fIbacklog\fR +/* \fBsmtp-sink\fR [\fIoptions\fR] [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* \fIbacklog\fR /* -/* \fBsmtp-sink\fR [\fB-cLpv\fR] [\fB-w \fIdelay\fR] -/* \fBunix:\fR\fIpathname\fR \fIbacklog\fR +/* \fBsmtp-sink\fR [\fIoptions\fR] \fBunix:\fR\fIpathname\fR \fIbacklog\fR /* DESCRIPTION /* \fIsmtp-sink\fR listens on the named host (or address) and port. /* It takes SMTP messages from the network and throws them away. @@ -18,17 +17,33 @@ /* Connections can be accepted on IPV4 endpoints or UNIX-domain sockets. /* IPV4 is the default. /* This program is the complement of the \fIsmtp-source\fR program. -/* .IP -c +/* +/* Arguments: +/* .IP \fB-c\fR /* Display a running counter that is updated whenever an SMTP /* QUIT command is executed. -/* .IP -L +/* .IP \fB-L\fR /* Speak LMTP rather than SMTP. -/* .IP -p +/* .IP "\fB-n \fIcount\fR" +/* Terminate after \fIcount\fR sessions. This is for testing purposes. +/* .IP \fB-p\fR /* Disable ESMTP command pipelining. -/* .IP -v +/* .IP \fB-P\fR +/* Change the server greeting so that it appears to come through +/* a CISCO PIX system. +/* .IP \fB-v\fR /* Show the SMTP conversations. -/* .IP "-w delay" +/* .IP "\fB-w \fIdelay\fR" /* Wait \fIdelay\fR seconds before responding to a DATA command. +/* .IP [\fBinet:\fR][\fIhost\fR]:\fIport\fR +/* Listen on network interface \fIhost\fR (default: any interface) +/* TCP port \fIport\fR. Both \fIhost\fR and \fIport\fR may be +/* specified in numeric or symbolic form. +/* .IP \fBunix:\fR\fIpathname\fR +/* Listen on the UNIX-domain socket at \fIpathname\fR. +/* .IP \fIbacklog\fR +/* The maximum length the queue of pending connections, +/* as defined by the listen(2) call. /* SEE ALSO /* smtp-source, SMTP/LMTP test message generator /* LICENSE @@ -77,6 +92,7 @@ typedef struct SINK_STATE { VSTREAM *stream; + VSTRING *buffer; int data_state; int (*read) (struct SINK_STATE *); int rcpts; @@ -90,17 +106,18 @@ typedef struct SINK_STATE { #define ST_CR_LF_DOT_CR_LF 5 static int var_tmout; -static int var_max_line_length; +static int var_max_line_length = 2048; static char *var_myhostname; -static VSTRING *buffer; static int command_read(SINK_STATE *); static int data_read(SINK_STATE *); static void disconnect(SINK_STATE *); static int count; static int counter; +static int max_count; static int disable_pipelining; static int fixed_delay; static int enable_lmtp; +static int pretend_pix; /* ehlo_response - respond to EHLO command */ @@ -197,11 +214,10 @@ static int data_read(SINK_STATE *state) struct data_trans *dp; /* - * We must avoid blocking I/O, so get out of here as soon as both the - * VSTREAM and kernel read buffers dry up. + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. */ - while (vstream_peek(state->stream) > 0 - || peekfd(vstream_fileno(state->stream)) > 0) { + for (;;) { if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) return (-1); for (dp = data_trans; dp->state != state->data_state; dp++) @@ -224,8 +240,17 @@ static int data_read(SINK_STATE *state) msg_info("."); dot_response(state); state->read = command_read; + state->data_state = ST_ANY; break; } + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + */ + if (vstream_peek(state->stream) <= 0 + && peekfd(vstream_fileno(state->stream)) <= 0) + return (0); } return (0); } @@ -258,9 +283,74 @@ static int command_read(SINK_STATE *state) { char *command; SINK_COMMAND *cmdp; + int ch; + struct cmd_trans { + int state; + int want; + int next_state; + }; + static struct cmd_trans cmd_trans[] = { + ST_ANY, '\r', ST_CR, + ST_CR, '\n', ST_CR_LF, + }; + struct cmd_trans *cp; - smtp_get(buffer, state->stream, var_max_line_length); - if ((command = strtok(vstring_str(buffer), " \t")) == 0) { + /* + * A read may result in EOF, but is never supposed to time out - a time + * out means that we were trying to read when no data was available. + */ + for (;;) { + if ((ch = VSTREAM_GETC(state->stream)) == VSTREAM_EOF) + return (-1); + + /* + * Sanity check. We don't want to store infinitely long commands. + */ + if (VSTRING_LEN(state->buffer) >= var_max_line_length) { + msg_warn("command line too long"); + return (-1); + } + VSTRING_ADDCH(state->buffer, ch); + + /* + * Try to match the current character desired by the state machine. + * If that fails, try to restart the machine with a match for its + * first state. + */ + for (cp = cmd_trans; cp->state != state->data_state; cp++) + /* void */ ; + if (ch == cp->want) + state->data_state = cp->next_state; + else if (ch == cmd_trans[0].want) + state->data_state = cmd_trans[0].next_state; + else + state->data_state = ST_ANY; + if (state->data_state == ST_CR_LF) + break; + + /* + * We must avoid blocking I/O, so get out of here as soon as both the + * VSTREAM and kernel read buffers dry up. + */ + if (vstream_peek(state->stream) <= 0 + && peekfd(vstream_fileno(state->stream)) <= 0) + return (0); + } + + /* + * Properly terminate the result, and reset the buffer write pointer for + * reading the next command. This is ugly, but not as ugly as trying to + * deal with all the early returns below. + */ + vstring_truncate(state->buffer, VSTRING_LEN(state->buffer) - 2); + VSTRING_TERMINATE(state->buffer); + state->data_state = ST_ANY; + VSTRING_RESET(state->buffer); + + /* + * Got a complete command line. Parse it. + */ + if ((command = strtok(vstring_str(state->buffer), " \t")) == 0) { smtp_printf(state->stream, "500 Error: unknown command"); return (0); } @@ -321,7 +411,10 @@ static void disconnect(SINK_STATE *state) { event_disable_readwrite(vstream_fileno(state->stream)); vstream_fclose(state->stream); + vstring_free(state->buffer); myfree((char *) state); + if (max_count > 0 && counter >= max_count) + exit(0); } /* connect_event - handle connection events */ @@ -350,9 +443,13 @@ static void connect_event(int unused_event, char *context) non_blocking(fd, NON_BLOCKING); state = (SINK_STATE *) mymalloc(sizeof(*state)); state->stream = vstream_fdopen(fd, O_RDWR); + state->buffer = vstring_alloc(1024); state->read = command_read; - state->data_state = 0; + state->data_state = ST_ANY; smtp_timeout_setup(state->stream, var_tmout); +if (pretend_pix) + smtp_printf(state->stream, "220 ********"); +else smtp_printf(state->stream, "220 %s ESMTP", var_myhostname); event_enable_read(fd, read_event, (char *) state); } @@ -362,7 +459,7 @@ static void connect_event(int unused_event, char *context) static void usage(char *myname) { - msg_fatal("usage: %s [-cLpv] [host]:port backlog", myname); + msg_fatal("usage: %s [-cLpPv] [-n count] [-w delay] [host]:port backlog", myname); } int main(int argc, char **argv) @@ -379,7 +476,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "cLpvw:")) > 0) { + while ((ch = GETOPT(argc, argv, "cLn:pPvw:")) > 0) { switch (ch) { case 'c': count++; @@ -387,9 +484,15 @@ int main(int argc, char **argv) case 'L': enable_lmtp = 1; break; + case 'n': + max_count = atoi(optarg); + break; case 'p': disable_pipelining = 1; break; + case 'P': + pretend_pix=1; + break; case 'v': msg_verbose++; break; @@ -409,7 +512,6 @@ int main(int argc, char **argv) /* * Initialize. */ - buffer = vstring_alloc(1024); var_myhostname = "smtp-sink"; if (strncmp(argv[optind], "unix:", 5) == 0) { sock = unix_listen(argv[optind] + 5, backlog, BLOCKING); diff --git a/gnu/dist/postfix/src/smtpstone/smtp-source.c b/gnu/dist/postfix/src/smtpstone/smtp-source.c index 6ab916429bd..d05caae779a 100644 --- a/gnu/dist/postfix/src/smtpstone/smtp-source.c +++ b/gnu/dist/postfix/src/smtpstone/smtp-source.c @@ -9,48 +9,59 @@ /* /* \fBsmtp-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR /* DESCRIPTION -/* smtp-source connects to the named host and port (default 25) -/* and sends one or more little messages to it, either sequentially +/* smtp-source connects to the named \fIhost\fR and TCP \fIport\fR +/* (default: port 25) +/* and sends one or more messages to it, either sequentially /* or in parallel. The program speaks either SMTP (default) or /* LMTP. Connections can be made to UNIX-domain and IPV4 servers. /* IPV4 is the default. /* -/* Options: -/* .IP -c +/* Arguments: +/* .IP \fB-c\fR /* Display a running counter that is incremented each time /* an SMTP DATA command completes. -/* .IP "-C count" +/* .IP "\fB-C \fIcount\fR" /* When a host sends RESET instead of SYN|ACK, try \fIcount\fR times /* before giving up. The default count is 1. Specify a larger count in /* order to work around a problem with TCP/IP stacks that send RESET /* when the listen queue is full. -/* .IP -d +/* .IP \fB-d\fR /* Don't disconnect after sending a message; send the next /* message over the same connection. -/* .IP "-f from" +/* .IP "\fB-f \fIfrom\fR" /* Use the specified sender address (default: <foo@myhostname>). -/* .IP -o +/* .IP \fB-o\fR /* Old mode: don't send HELO, and don't send message headers. -/* .IP "-l length" -/* Send \fIlength\fR bytes as message payload. -/* .IP -L +/* .IP "\fB-l \fIlength\fR" +/* Send \fIlength\fR bytes as message payload. The length does not +/* include message headers. +/* .IP \fB-L\fR /* Speak LMTP rather than SMTP. -/* .IP "-m message_count" +/* .IP "\fB-m \fImessage_count\fR" /* Send the specified number of messages (default: 1). -/* .IP "-r recipient_count" +/* .IP "\fB-r \fIrecipient_count\fR" /* Send the specified number of recipients per transaction (default: 1). /* Recipient names are generated by prepending a number to the -/* recipient address. The default is one recipient per transaction. -/* .IP "-s session_count" +/* recipient address. +/* .IP "\fB-s \fIsession_count\fR" /* Run the specified number of SMTP sessions in parallel (default: 1). -/* .IP "-t to" +/* .IP "\fB-S \fIsubject\fR" +/* Send mail with the named subject line (default: none). +/* .IP "\fB-t \fIto\fR" /* Use the specified recipient address (default: <foo@myhostname>). -/* .IP "-R interval" +/* .IP "\fB-R \fIinterval\fR" /* Wait for a random period of time 0 <= n <= interval between messages. /* Suspending one thread does not affect other delivery threads. -/* .IP "-w interval" +/* .IP "\fB-w \fIinterval\fR" /* Wait a fixed time between messages. /* Suspending one thread does not affect other delivery threads. +/* .IP [\fBinet:\fR]\fIhost\fR[:\fIport\fR] +/* Connect via TCP to \fIhost\fR port \fIport\fR. The default +/* port is \fBsmtp\fR. +/* .IP \fBunix:\fIpathname\fR +/* Connect to the UNIX-domain socket at \fIpathname\fR. +/* BUGS +/* No SMTP command pipelining support. /* LICENSE /* .ad /* .fi @@ -156,6 +167,7 @@ static int connect_count = 1; static int random_delay = 0; static int fixed_delay = 0; static int talk_lmtp = 0; +static char *subject = 0; static void enqueue_connect(SESSION *); static void start_connect(SESSION *); @@ -644,6 +656,8 @@ static void data_done(int unused_event, char *context) smtp_printf(session->stream, "Date: %s", mydate); smtp_printf(session->stream, "Message-Id: <%04x.%04x.%04x@%s>", mypid, vstream_fileno(session->stream), message_count, var_myhostname); + if (subject) + smtp_printf(session->stream, "Subject: %s", subject); smtp_fputs("", 0, session->stream); } @@ -738,7 +752,7 @@ static void quit_done(int unused_event, char *context) static void usage(char *myname) { - msg_fatal("usage: %s -s sess -l msglen -m msgs -c -C count -d -f from -o -t to -R delay -v -w delay host[:port]", myname); + msg_fatal("usage: %s -s sess -l msglen -m msgs -c -C count -d -f from -o -t to -r rcptcount -R delay -v -w delay host[:port]", myname); } /* main - parse JCL and start the machine */ @@ -760,7 +774,7 @@ int main(int argc, char **argv) /* * Parse JCL. */ - while ((ch = GETOPT(argc, argv, "cC:df:l:Lm:or:R:s:t:vw:")) > 0) { + while ((ch = GETOPT(argc, argv, "cC:df:l:Lm:or:R:s:S:t:vw:")) > 0) { switch (ch) { case 'c': count++; @@ -781,6 +795,7 @@ int main(int argc, char **argv) message_data = mymalloc(message_length); memset(message_data, 'X', message_length); for (i = 80; i < message_length; i += 80) { + message_data[i - 80] = "0123456789"[(i / 80) % 10]; message_data[i - 2] = '\r'; message_data[i - 1] = '\n'; } @@ -808,6 +823,9 @@ int main(int argc, char **argv) if ((sessions = atoi(optarg)) <= 0) usage(argv[0]); break; + case 'S': + subject = optarg; + break; case 't': recipient = optarg; break; @@ -847,7 +865,7 @@ int main(int argc, char **argv) } else { if (strncmp(argv[optind], "inet:", 5) == 0) argv[optind] += 5; - if ((port = split_at(host = argv[optind], ':')) == 0) + if ((port = split_at(host = argv[optind], ':')) == 0 || *port == 0) port = "smtp"; memset((char *) &sin, 0, sizeof(sin)); sin.sin_family = AF_INET; diff --git a/gnu/dist/postfix/src/trivial-rewrite/Makefile.in b/gnu/dist/postfix/src/trivial-rewrite/Makefile.in index 1065ce0cfea..7f33c4c376f 100644 --- a/gnu/dist/postfix/src/trivial-rewrite/Makefile.in +++ b/gnu/dist/postfix/src/trivial-rewrite/Makefile.in @@ -70,6 +70,7 @@ resolve.o: ../../include/split_at.h resolve.o: ../../include/mail_params.h resolve.o: ../../include/mail_proto.h resolve.o: ../../include/iostuff.h +resolve.o: ../../include/attr.h resolve.o: ../../include/mail_addr.h resolve.o: ../../include/rewrite_clnt.h resolve.o: ../../include/resolve_local.h @@ -90,6 +91,7 @@ rewrite.o: ../../include/split_at.h rewrite.o: ../../include/mail_params.h rewrite.o: ../../include/mail_proto.h rewrite.o: ../../include/iostuff.h +rewrite.o: ../../include/attr.h rewrite.o: ../../include/resolve_local.h rewrite.o: ../../include/tok822.h rewrite.o: ../../include/resolve_clnt.h @@ -108,6 +110,8 @@ transport.o: ../../include/vstream.h transport.o: ../../include/argv.h transport.o: ../../include/mail_params.h transport.o: ../../include/maps.h +transport.o: ../../include/match_parent_style.h +transport.o: ../../include/match_ops.h transport.o: transport.h trivial-rewrite.o: trivial-rewrite.c trivial-rewrite.o: ../../include/sys_defs.h @@ -123,6 +127,7 @@ trivial-rewrite.o: ../../include/argv.h trivial-rewrite.o: ../../include/mail_params.h trivial-rewrite.o: ../../include/mail_proto.h trivial-rewrite.o: ../../include/iostuff.h +trivial-rewrite.o: ../../include/attr.h trivial-rewrite.o: ../../include/resolve_local.h trivial-rewrite.o: ../../include/mail_conf.h trivial-rewrite.o: ../../include/resolve_clnt.h diff --git a/gnu/dist/postfix/src/trivial-rewrite/resolve.c b/gnu/dist/postfix/src/trivial-rewrite/resolve.c index bd99fa61ae3..ba446301934 100644 --- a/gnu/dist/postfix/src/trivial-rewrite/resolve.c +++ b/gnu/dist/postfix/src/trivial-rewrite/resolve.c @@ -257,7 +257,9 @@ int resolve_proto(VSTREAM *stream) { int flags; - if (mail_scan(stream, "%s", query) != 1) + if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, query, + ATTR_TYPE_END) != 1) return (-1); resolve_addr(STR(query), channel, nexthop, nextrcpt, &flags); @@ -266,8 +268,12 @@ int resolve_proto(VSTREAM *stream) msg_info("%s -> (`%s' `%s' `%s' `%d')", STR(query), STR(channel), STR(nexthop), STR(nextrcpt), flags); - mail_print(stream, "%s %s %s %d", - STR(channel), STR(nexthop), STR(nextrcpt), flags); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_TRANSPORT, STR(channel), + ATTR_TYPE_STR, MAIL_ATTR_NEXTHOP, STR(nexthop), + ATTR_TYPE_STR, MAIL_ATTR_RECIP, STR(nextrcpt), + ATTR_TYPE_NUM, MAIL_ATTR_FLAGS, flags, + ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write resolver reply: %m"); diff --git a/gnu/dist/postfix/src/trivial-rewrite/rewrite.c b/gnu/dist/postfix/src/trivial-rewrite/rewrite.c index 80ab11c252b..e1ae59cf751 100644 --- a/gnu/dist/postfix/src/trivial-rewrite/rewrite.c +++ b/gnu/dist/postfix/src/trivial-rewrite/rewrite.c @@ -203,7 +203,10 @@ void rewrite_addr(char *ruleset, char *addr, VSTRING *result) int rewrite_proto(VSTREAM *stream) { - if (mail_scan(stream, "%s %s", ruleset, address) != 2) + if (attr_scan(stream, ATTR_FLAG_STRICT, + ATTR_TYPE_STR, MAIL_ATTR_RULE, ruleset, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, address, + ATTR_TYPE_END) != 2) return (-1); rewrite_addr(vstring_str(ruleset), vstring_str(address), result); @@ -212,7 +215,9 @@ int rewrite_proto(VSTREAM *stream) msg_info("`%s' `%s' -> `%s'", vstring_str(ruleset), vstring_str(address), vstring_str(result)); - mail_print(stream, "%s", vstring_str(result)); + attr_print(stream, ATTR_FLAG_NONE, + ATTR_TYPE_STR, MAIL_ATTR_ADDR, vstring_str(result), + ATTR_TYPE_END); if (vstream_fflush(stream) != 0) { msg_warn("write rewrite reply: %m"); diff --git a/gnu/dist/postfix/src/trivial-rewrite/transport.c b/gnu/dist/postfix/src/trivial-rewrite/transport.c index baf55f69963..379e2395f0a 100644 --- a/gnu/dist/postfix/src/trivial-rewrite/transport.c +++ b/gnu/dist/postfix/src/trivial-rewrite/transport.c @@ -62,12 +62,14 @@ #include <mail_params.h> #include <maps.h> +#include <match_parent_style.h> /* Application-specific. */ #include "transport.h" static MAPS *transport_path; +static int transport_match_parent_style; /* transport_init - pre-jail initialization */ @@ -77,6 +79,7 @@ void transport_init(void) msg_panic("transport_init: repeated call"); transport_path = maps_create("transport", var_transport_maps, DICT_FLAG_LOCK); + transport_match_parent_style = match_parent_style(VAR_TRANSPORT_MAPS); } /* transport_lookup - map a transport domain */ @@ -85,6 +88,7 @@ int transport_lookup(const char *domain, VSTRING *channel, VSTRING *nexthop) { char *low_domain = lowercase(mystrdup(domain)); const char *name; + const char *next; const char *value; const char *host; char *saved_value; @@ -113,7 +117,7 @@ int transport_lookup(const char *domain, VSTRING *channel, VSTRING *nexthop) * Specify if a key is partial or full, to avoid matching partial keys with * regular expressions. */ - for (name = low_domain; name != 0; name = strchr(name + 1, '.')) { + for (name = low_domain; /* void */; name = next) { if ((value = maps_find(transport_path, name, maps_flag)) != 0) { saved_value = mystrdup(value); if ((host = split_at(saved_value, ':')) == 0 || *host == 0) @@ -132,6 +136,10 @@ int transport_lookup(const char *domain, VSTRING *channel, VSTRING *nexthop) } else if (dict_errno != 0) { msg_fatal("transport table lookup problem"); } + if ((next = strchr(name + 1, '.')) == 0) + break; + if (transport_match_parent_style == MATCH_FLAG_PARENT) + next++; maps_flag = PARTIAL; } myfree(low_domain); diff --git a/gnu/dist/postfix/src/trivial-rewrite/trivial-rewrite.c b/gnu/dist/postfix/src/trivial-rewrite/trivial-rewrite.c index 61624ada5e4..71dc8b2f1dd 100644 --- a/gnu/dist/postfix/src/trivial-rewrite/trivial-rewrite.c +++ b/gnu/dist/postfix/src/trivial-rewrite/trivial-rewrite.c @@ -80,7 +80,7 @@ /* .fi /* .IP \fBlocal_transport\fR /* Where to deliver mail for destinations that match $\fBmydestination\fR -/* or $\fBinet_interfaces\fR. +/* or $\fBinet_interfaces\fR. /* The default transport is \fBlocal\fR. /* .sp /* Syntax is \fItransport\fR:\fInexthop\fR; see \fBtransport\fR(5) @@ -92,6 +92,10 @@ /* .sp /* Syntax is \fItransport\fR:\fInexthop\fR; see \fBtransport\fR(5) /* for details. The :\fInexthop\fR part is optional. +/* .IP \fBparent_domain_matches_subdomains\fR +/* List of Postfix features that use \fIdomain.name\fR patterns +/* to match \fIsub.domain.name\fR (as opposed to +/* requiring \fI.domain.name\fR patterns). /* .IP \fBrelayhost\fR /* The default host to send non-local mail to when no entry is matched /* in the \fBtransport\fR(5) table. @@ -181,7 +185,9 @@ static void rewrite_service(VSTREAM *stream, char *unused_service, char **argv) * dedicated to address rewriting. All connection-management stuff is * handled by the common code in multi_server.c. */ - if (mail_scan(stream, "%s", command) == 1) { + if (attr_scan(stream, ATTR_FLAG_STRICT | ATTR_FLAG_MORE, + ATTR_TYPE_STR, MAIL_ATTR_REQ, command, + ATTR_TYPE_END) == 1) { if (strcmp(vstring_str(command), REWRITE_ADDR) == 0) { status = rewrite_proto(stream); } else if (strcmp(vstring_str(command), RESOLVE_ADDR) == 0) { diff --git a/gnu/dist/postfix/src/util/alldig.c b/gnu/dist/postfix/src/util/alldig.c new file mode 100644 index 00000000000..c4815f68c67 --- /dev/null +++ b/gnu/dist/postfix/src/util/alldig.c @@ -0,0 +1,48 @@ +/*++ +/* NAME +/* alldig 3 +/* SUMMARY +/* predicate if string is all numerical +/* SYNOPSIS +/* #include <stringops.h> +/* +/* int alldig(string) +/* const char *string; +/* DESCRIPTION +/* alldig() determines if its argument is an all-numerical string. +/* SEE ALSO +/* An alldig() routine appears in Brian W. Kernighan, P.J. Plauger: +/* "Software Tools", Addison-Wesley 1976. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <ctype.h> + +/* Utility library. */ + +#include <stringops.h> + +/* alldig - return true if string is all digits */ + +int alldig(const char *string) +{ + const char *cp; + + if (*string == 0) + return (0); + for (cp = string; *cp != 0; cp++) + if (!ISDIGIT(*cp)) + return (0); + return (1); +} diff --git a/gnu/dist/postfix/src/util/attr.h b/gnu/dist/postfix/src/util/attr.h index e81381ed337..c656dadf8e1 100644 --- a/gnu/dist/postfix/src/util/attr.h +++ b/gnu/dist/postfix/src/util/attr.h @@ -5,19 +5,80 @@ /* NAME /* attr 3h /* SUMMARY -/* attribute list manager +/* attribute list manipulations /* SYNOPSIS -/* #include <attr.h> -/* DESCRIPTION -/* .nf +/* #include "attr.h" + DESCRIPTION + .nf /* - * External interface. + * System library. */ -extern void attr_enter(HTABLE *, const char *, const char *); -extern void attr_free(HTABLE *); +#include <stdarg.h> -#define attr_find(table, name) htable_find((table), (name)) + /* + * Utility library. + */ +#include <vstream.h> + + /* + * Attribute types. See attr_scan(3) for documentation. + */ +#define ATTR_TYPE_END 0 /* end of data */ +#define ATTR_TYPE_NUM 1 /* Unsigned integer */ +#define ATTR_TYPE_STR 2 /* Character string */ +#define ATTR_TYPE_HASH 3 /* Hash table */ +#define ATTR_TYPE_LONG 4 /* Unsigned long */ + + /* + * Flags that control processing. See attr_scan(3) for documentation. + */ +#define ATTR_FLAG_NONE 0 +#define ATTR_FLAG_MISSING (1<<0) /* Flag missing attribute */ +#define ATTR_FLAG_EXTRA (1<<1) /* Flag spurious attribute */ +#define ATTR_FLAG_MORE (1<<2) /* Don't skip or terminate */ + +#define ATTR_FLAG_STRICT (ATTR_FLAG_MISSING | ATTR_FLAG_EXTRA) +#define ATTR_FLAG_ALL (07) + +#define attr_print attr_print0 +#define attr_vprint attr_vprint0 +#define attr_scan attr_scan0 +#define attr_vscan attr_vscan0 + + /* + * attr_print64.c. + */ +extern int attr_print64(VSTREAM *, int,...); +extern int attr_vprint64(VSTREAM *, int, va_list); + + /* + * attr_scan64.c. + */ +extern int attr_scan64(VSTREAM *, int,...); +extern int attr_vscan64(VSTREAM *, int, va_list); + + /* + * attr_print0.c. + */ +extern int attr_print0(VSTREAM *, int,...); +extern int attr_vprint0(VSTREAM *, int, va_list); + + /* + * attr_scan0.c. + */ +extern int attr_scan0(VSTREAM *, int,...); +extern int attr_vscan0(VSTREAM *, int, va_list); + + /* + * Attribute names for testing the compatibility of the read and write + * routines. + */ +#ifdef TEST +#define ATTR_NAME_NUM "number" +#define ATTR_NAME_STR "string" +#define ATTR_NAME_LONG "long_number" +#endif /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/util/attr_print0.c b/gnu/dist/postfix/src/util/attr_print0.c new file mode 100644 index 00000000000..c39b72104da --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_print0.c @@ -0,0 +1,209 @@ +/*++ +/* NAME +/* attr_print0 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_print0(fp, flags, type, name, ...) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint0(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print0() takes zero or more (name, value) simple attributes +/* or (name, count, value) list attributes, and converts its input +/* to a byte stream that can be recovered with attr_scan0(). The stream +/* is not flushed. +/* +/* attr_vprint0() provides an alternate interface that is convenient +/* for calling from within variadoc functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print0() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan0(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print0() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP type +/* The type determines the arguments that follow. +/* .RS +/* .IP "ATTR_TYPE_NUM (char *, int)" +/* This argument is followed by an attribute name and an integer. +/* .IP "ATTR_TYPE_NUM (char *, long)" +/* This argument is followed by an attribute name and a long integer. +/* .IP "ATTR_TYPE_STR (char *, char *)" +/* This argument is followed by an attribute name and a null-terminated +/* string. +/* .IP "ATTR_TYPE_HASH (HTABLE *)" +/* The content of the hash table is sent as a sequence of string-valued +/* attributes with names equal to the hash table lookup key. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan0(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <attr.h> + +/* attr_vprint0 - send attribute list to stream */ + +int attr_vprint0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print0"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_NUM: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + int_val = va_arg(ap, int); + vstream_fprintf(fp, "%u", (unsigned) int_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + long_val = va_arg(ap, unsigned long); + vstream_fprintf(fp, "%lu", (unsigned long) long_val); + VSTREAM_PUTC('\0', fp); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + vstream_fwrite(fp, attr_name, strlen(attr_name) + 1); + str_val = va_arg(ap, char *); + vstream_fwrite(fp, str_val, strlen(str_val) + 1); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_HASH: + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + vstream_fwrite(fp, ht[0]->key, strlen(ht[0]->key) + 1); + vstream_fwrite(fp, ht[0]->value, strlen(ht[0]->value) + 1); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, ht[0]->value); + } + myfree((char *) ht_info_list); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\0', fp); + return (vstream_ferror(fp)); +} + +int attr_print0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint0(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include <msg_vstream.h> + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711, + ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234, + ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee", + ATTR_TYPE_HASH, table, + ATTR_TYPE_END); + attr_print0(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711, + ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234, + ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee", + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/gnu/dist/postfix/src/util/attr_print64.c b/gnu/dist/postfix/src/util/attr_print64.c new file mode 100644 index 00000000000..1fc37d7b346 --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_print64.c @@ -0,0 +1,253 @@ +/*++ +/* NAME +/* attr_print64 3 +/* SUMMARY +/* send attributes over byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_print64(fp, flags, type, name, ...) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vprint64(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_print64() takes zero or more (name, value) simple attributes +/* or (name, count, value) list attributes, and converts its input +/* to a byte stream that can be recovered with attr_scan64(). The stream +/* is not flushed. +/* +/* attr_vprint64() provides an alternate interface that is convenient +/* for calling from within variadoc functions. +/* +/* Attributes are sent in the requested order as specified with the +/* attr_print64() argument list. This routine satisfies the formatting +/* rules as outlined in attr_scan64(3). +/* +/* Arguments: +/* .IP fp +/* Stream to write the result to. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MORE +/* After sending the requested attributes, leave the output stream in +/* a state that is usable for more attribute sending operations on +/* the same output attribute list. +/* By default, attr_print64() automatically appends an attribute list +/* terminator when it has sent the last requested attribute. +/* .RE +/* .IP type +/* The type determines the arguments that follow. +/* .RS +/* .IP "ATTR_TYPE_NUM (char *, int)" +/* This argument is followed by an attribute name and an integer. +/* .IP "ATTR_TYPE_NUM (char *, long)" +/* This argument is followed by an attribute name and a long integer. +/* .IP "ATTR_TYPE_STR (char *, char *)" +/* This argument is followed by an attribute name and a null-terminated +/* string. +/* .IP "ATTR_TYPE_HASH (HTABLE *)" +/* The content of the hash table is sent as a sequence of string-valued +/* attributes with names equal to the hash table lookup key. +/* .IP ATTR_TYPE_END +/* This terminates the attribute list. +/* .RE +/* DIAGNOSTICS +/* The result value is 0 in case of success, VSTREAM_EOF in case +/* of trouble. +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_scan64(3) recover attributes from byte stream +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <htable.h> +#include <base64_code.h> +#include <attr.h> + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_print64_str - encode and send attribute information */ + +static void attr_print64_str(VSTREAM *fp, const char *str, int len) +{ + static VSTRING *base64_buf; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + base64_encode(base64_buf, str, len); + vstream_fputs(STR(base64_buf), fp); +} + +static void attr_print64_num(VSTREAM *fp, unsigned num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%u", num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +static void attr_print64_long_num(VSTREAM *fp, unsigned long long_num) +{ + static VSTRING *plain; + + if (plain == 0) + plain = vstring_alloc(10); + + vstring_sprintf(plain, "%lu", long_num); + attr_print64_str(fp, STR(plain), LEN(plain)); +} + +/* attr_vprint64 - send attribute list to stream */ + +int attr_vprint64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_print64"; + int attr_type; + char *attr_name; + unsigned int_val; + unsigned long long_val; + char *str_val; + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Iterate over all (type, name, value) triples, and produce output on + * the fly. + */ + while ((attr_type = va_arg(ap, int)) != ATTR_TYPE_END) { + switch (attr_type) { + case ATTR_TYPE_NUM: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + int_val = va_arg(ap, int); + VSTREAM_PUTC(':', fp); + attr_print64_num(fp, (unsigned) int_val); + if (msg_verbose) + msg_info("send attr %s = %u", attr_name, int_val); + break; + case ATTR_TYPE_LONG: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + long_val = va_arg(ap, long); + VSTREAM_PUTC(':', fp); + attr_print64_long_num(fp, (unsigned long) long_val); + if (msg_verbose) + msg_info("send attr %s = %lu", attr_name, long_val); + break; + case ATTR_TYPE_STR: + attr_name = va_arg(ap, char *); + attr_print64_str(fp, attr_name, strlen(attr_name)); + str_val = va_arg(ap, char *); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, str_val, strlen(str_val)); + if (msg_verbose) + msg_info("send attr %s = %s", attr_name, str_val); + break; + case ATTR_TYPE_HASH: + ht_info_list = htable_list(va_arg(ap, HTABLE *)); + for (ht = ht_info_list; *ht; ht++) { + attr_print64_str(fp, ht[0]->key, strlen(ht[0]->key)); + VSTREAM_PUTC(':', fp); + attr_print64_str(fp, ht[0]->value, strlen(ht[0]->value)); + if (msg_verbose) + msg_info("send attr name %s value %s", + ht[0]->key, ht[0]->value); + if (ht[1] != 0) + VSTREAM_PUTC('\n', fp); + } + myfree((char *) ht_info_list); + break; + default: + msg_panic("%s: unknown type code: %d", myname, attr_type); + } + VSTREAM_PUTC('\n', fp); + } + if ((flags & ATTR_FLAG_MORE) == 0) + VSTREAM_PUTC('\n', fp); + return (vstream_ferror(fp)); +} + +int attr_print64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vprint64(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include <msg_vstream.h> + +int main(int unused_argc, char **argv) +{ + HTABLE *table = htable_create(1); + + msg_vstream_init(argv[0], VSTREAM_ERR); + msg_verbose = 1; + htable_enter(table, "foo-name", mystrdup("foo-value")); + htable_enter(table, "bar-name", mystrdup("bar-value")); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711, + ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234, + ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee", + ATTR_TYPE_HASH, table, + ATTR_TYPE_END); + attr_print64(VSTREAM_OUT, ATTR_FLAG_NONE, + ATTR_TYPE_NUM, ATTR_NAME_NUM, 4711, + ATTR_TYPE_LONG, ATTR_NAME_LONG, 1234, + ATTR_TYPE_STR, ATTR_NAME_STR, "whoopee", + ATTR_TYPE_END); + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + htable_free(table, myfree); + return (0); +} + +#endif diff --git a/gnu/dist/postfix/src/util/attr_scan.ref b/gnu/dist/postfix/src/util/attr_scan.ref new file mode 100644 index 00000000000..cd06a27d82d --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_scan.ref @@ -0,0 +1,36 @@ +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_print: send attr name foo-name value foo-value +./attr_print: send attr name bar-name value bar-value +./attr_print: send attr number = 4711 +./attr_print: send attr string = whoopee +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: foo-name +./attr_scan: input attribute value: foo-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: bar-name +./attr_scan: input attribute value: bar-value +./attr_scan: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan: input attribute name: (end) +./attr_scan: unknown_stream: wanted attribute: number +./attr_scan: input attribute name: number +./attr_scan: input attribute value: 4711 +./attr_scan: unknown_stream: wanted attribute: string +./attr_scan: input attribute name: string +./attr_scan: input attribute value: whoopee +./attr_scan: unknown_stream: wanted attribute: (list terminator) +./attr_scan: input attribute name: (end) +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value +number 4711 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value diff --git a/gnu/dist/postfix/src/util/attr_scan0.c b/gnu/dist/postfix/src/util/attr_scan0.c new file mode 100644 index 00000000000..ea4cf880886 --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_scan0.c @@ -0,0 +1,441 @@ +/*++ +/* NAME +/* attr_scan0 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_scan0(fp, flags, type, name, ...) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan0(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_scan0() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print0(). +/* +/* attr_vscan0() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== simple-attr* null +/* .br +/* simple-attr :== attr-name null attr-value null +/* .br +/* attr-name :== any string not containing null +/* .br +/* attr-value :== any string not containing null +/* .br +/* null :== the ASCII null character +/* .in +/* +/* All attribute names and attribute values are sent as null terminated +/* strings. Each string must be no longer than 2*var_line_limit +/* characters. The formatting rules favor implementations in C. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan0() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan0() operations from the +/* same input attribute list. +/* By default, attr_scan0() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP type +/* The type argument determines the arguments that follow. +/* .RS +/* .IP "ATTR_TYPE_NUM (char *, int *)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "ATTR_TYPE_LONG (char *, long *)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "ATTR_TYPE_STR (char *, VSTRING *)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "ATTR_TYPE_HASH (HTABLE *)" +/* All further input attributes are processed as string attributes. +/* No specific attribute sequence is enforced. +/* All attributes up to the attribute list terminator are read, +/* but only the first instance of each attribute is stored. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* N.B. This construct must be followed by an ATTR_TYPE_END argument. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* ATTR_TYPE_HASH accepts attributes with arbitrary names from possibly +/* untrusted sources. This is unsafe, unless the resulting table is +/* queried only with known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan0() and attr_vscan0() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print0(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <vstring_vstream.h> +#include <htable.h> +#include <attr.h> + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan0_string - pull a string from the input stream */ + +static int attr_scan0_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 2; + int ch; + + if ((ch = vstring_get_null(plain_buf, fp)) == VSTREAM_EOF) { + msg_warn("premature end-of-input from %s while reading %s", + VSTREAM_PATH(fp), context); + return (-1); + } + if (ch != 0) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan0_number - pull a number from the input stream */ + +static int attr_scan0_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan0_long_number - pull a number from the input stream */ + +static int attr_scan0_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan0_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan0 - receive attribute list from stream */ + +int attr_vscan0(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan0"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + if (va_arg(ap, int) !=ATTR_TYPE_END) + msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", + myname); + } else { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + for (;;) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan0_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("spurious attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + (void) attr_scan0_string(fp, str_buf, "input attribute value"); + } + + /* + * Do the requested conversion. + */ + switch (wanted_type) { + case ATTR_TYPE_NUM: + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan0_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_LONG: + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan0_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_STR: + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan0_string(fp, string, + "input attribute value")) < 0) + return (-1); + break; + case ATTR_TYPE_HASH: + if ((ch = attr_scan0_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan0 - read attribute list from stream */ + +int attr_scan0(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan0(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan0 test + * program. + */ +#include <msg_vstream.h> + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val, + ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, + ATTR_TYPE_STR, ATTR_NAME_STR, str_val, + ATTR_TYPE_HASH, table, + ATTR_TYPE_END)) > 3) { + vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); + myfree((char *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan0(VSTREAM_IN, + ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val, + ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, + ATTR_TYPE_STR, ATTR_NAME_STR, str_val, + ATTR_TYPE_END)) == 3) { + vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); + myfree((char *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/gnu/dist/postfix/src/util/attr_scan0.ref b/gnu/dist/postfix/src/util/attr_scan0.ref new file mode 100644 index 00000000000..105382fd1ef --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_scan0.ref @@ -0,0 +1,46 @@ +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_print0: send attr name foo-name value foo-value +./attr_print0: send attr name bar-name value bar-value +./attr_print0: send attr number = 4711 +./attr_print0: send attr long_number = 1234 +./attr_print0: send attr string = whoopee +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan0: input attribute name: foo-name +./attr_scan0: input attribute value: foo-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan0: input attribute name: bar-name +./attr_scan0: input attribute value: bar-value +./attr_scan0: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan0: input attribute name: (end) +./attr_scan0: unknown_stream: wanted attribute: number +./attr_scan0: input attribute name: number +./attr_scan0: input attribute value: 4711 +./attr_scan0: unknown_stream: wanted attribute: long_number +./attr_scan0: input attribute name: long_number +./attr_scan0: input attribute value: 1234 +./attr_scan0: unknown_stream: wanted attribute: string +./attr_scan0: input attribute name: string +./attr_scan0: input attribute value: whoopee +./attr_scan0: unknown_stream: wanted attribute: (list terminator) +./attr_scan0: input attribute name: (end) +number 4711 +long_number 1234 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value +number 4711 +long_number 1234 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value diff --git a/gnu/dist/postfix/src/util/attr_scan64.c b/gnu/dist/postfix/src/util/attr_scan64.c new file mode 100644 index 00000000000..d8631ef1cac --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_scan64.c @@ -0,0 +1,506 @@ +/*++ +/* NAME +/* attr_scan64 3 +/* SUMMARY +/* recover attributes from byte stream +/* SYNOPSIS +/* #include <attr.h> +/* +/* int attr_scan64(fp, flags, type, name, ...) +/* VSTREAM fp; +/* int flags; +/* int type; +/* char *name; +/* +/* int attr_vscan64(fp, flags, ap) +/* VSTREAM fp; +/* int flags; +/* va_list ap; +/* DESCRIPTION +/* attr_scan64() takes zero or more (name, value) request attributes +/* and recovers the attribute values from the byte stream that was +/* possibly generated by attr_print64(). +/* +/* attr_vscan64() provides an alternative interface that is convenient +/* for calling from within a variadic function. +/* +/* The input stream is formatted as follows, where (item)* stands +/* for zero or more instances of the specified item, and where +/* (item1 | item2) stands for choice: +/* +/* .in +5 +/* attr-list :== simple-attr* newline +/* .br +/* simple-attr :== attr-name colon attr-value newline +/* .br +/* attr-name :== any base64 encoded string +/* .br +/* attr-value :== any base64 encoded string +/* .br +/* colon :== the ASCII colon character +/* .br +/* newline :== the ASCII newline character +/* .in +/* +/* All attribute names and attribute values are sent as base64-encoded +/* strings. Each base64 encoding must be no longer than 2*var_line_limit +/* characters. The formatting rules aim to make implementations in PERL +/* and other languages easy. +/* +/* Normally, attributes must be received in the sequence as specified with +/* the attr_scan64() argument list. The input stream may contain additional +/* attributes at any point in the input stream, including additional +/* instances of requested attributes. +/* +/* Additional input attributes or input attribute instances are silently +/* skipped over, unless the ATTR_FLAG_EXTRA processing flag is specified +/* (see below). This allows for some flexibility in the evolution of +/* protocols while still providing the option of being strict where +/* this is desirable. +/* +/* Arguments: +/* .IP fp +/* Stream to recover the input attributes from. +/* .IP flags +/* The bit-wise OR of zero or more of the following. +/* .RS +/* .IP ATTR_FLAG_MISSING +/* Log a warning when the input attribute list terminates before all +/* requested attributes are recovered. It is always an error when the +/* input stream ends without the newline attribute list terminator. +/* .IP ATTR_FLAG_EXTRA +/* Log a warning and stop attribute recovery when the input stream +/* contains an attribute that was not requested. This includes the +/* case of additional instances of a requested attribute. +/* .IP ATTR_FLAG_MORE +/* After recovering the requested attributes, leave the input stream +/* in a state that is usable for more attr_scan64() operations from the +/* same input attribute list. +/* By default, attr_scan64() skips forward past the input attribute list +/* terminator. +/* .IP ATTR_FLAG_STRICT +/* For convenience, this value combines both ATTR_FLAG_MISSING and +/* ATTR_FLAG_EXTRA. +/* .IP ATTR_FLAG_NONE +/* For convenience, this value requests none of the above. +/* .RE +/* .IP type +/* The type argument determines the arguments that follow. +/* .RS +/* .IP "ATTR_TYPE_NUM (char *, int *)" +/* This argument is followed by an attribute name and an integer pointer. +/* .IP "ATTR_TYPE_LONG (char *, long *)" +/* This argument is followed by an attribute name and a long pointer. +/* .IP "ATTR_TYPE_STR (char *, VSTRING *)" +/* This argument is followed by an attribute name and a VSTRING pointer. +/* .IP "ATTR_TYPE_HASH (HTABLE *)" +/* All further input attributes are processed as string attributes. +/* No specific attribute sequence is enforced. +/* All attributes up to the attribute list terminator are read, +/* but only the first instance of each attribute is stored. +/* .sp +/* The attribute string values are stored in the hash table under +/* keys equal to the attribute name (obtained from the input stream). +/* Values from the input stream are added to the hash table. Existing +/* hash table entries are not replaced. +/* .sp +/* N.B. This construct must be followed by an ATTR_TYPE_END argument. +/* .IP ATTR_TYPE_END +/* This argument terminates the requested attribute list. +/* .RE +/* BUGS +/* ATTR_TYPE_HASH accepts attributes with arbitrary names from possibly +/* untrusted sources. This is unsafe, unless the resulting table is +/* queried only with known to be good attribute names. +/* DIAGNOSTICS +/* attr_scan64() and attr_vscan64() return -1 when malformed input is +/* detected (string too long, incomplete line, missing end marker). +/* Otherwise, the result value is the number of attributes that were +/* successfully recovered from the input stream (a hash table counts +/* as the number of entries stored into the table). +/* +/* Panic: interface violation. All system call errors are fatal. +/* SEE ALSO +/* attr_print64(3) send attributes over byte stream. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <string.h> +#include <stdio.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstream.h> +#include <vstring.h> +#include <htable.h> +#include <base64_code.h> +#include <attr.h> + +/* Application specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* attr_scan64_string - pull a string from the input stream */ + +static int attr_scan64_string(VSTREAM *fp, VSTRING *plain_buf, const char *context) +{ + static VSTRING *base64_buf = 0; + extern int var_line_limit; /* XXX */ + int limit = var_line_limit * 2; + int ch; + + if (base64_buf == 0) + base64_buf = vstring_alloc(10); + + VSTRING_RESET(base64_buf); + while ((ch = VSTREAM_GETC(fp)) != ':' && ch != '\n') { + if (ch == VSTREAM_EOF) { + msg_warn("premature end-of-input from %s while reading %s", + VSTREAM_PATH(fp), context); + return (-1); + } + VSTRING_ADDCH(base64_buf, ch); +#if 0 + if (LEN(base64_buf) > limit) { + msg_warn("string length > %d characters from %s while reading %s", + limit, VSTREAM_PATH(fp), context); + return (-1); + } +#endif + } + VSTRING_TERMINATE(base64_buf); + if (base64_decode(plain_buf, STR(base64_buf), LEN(base64_buf)) == 0) { + msg_warn("malformed base64 data from %s: %.100s", + VSTREAM_PATH(fp), STR(base64_buf)); + return (-1); + } + if (msg_verbose) + msg_info("%s: %s", context, *STR(plain_buf) ? STR(plain_buf) : "(end)"); + return (ch); +} + +/* attr_scan64_number - pull a number from the input stream */ + +static int attr_scan64_number(VSTREAM *fp, unsigned *ptr, VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%u%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_scan64_long_number - pull a number from the input stream */ + +static int attr_scan64_long_number(VSTREAM *fp, unsigned long *ptr, + VSTRING *str_buf, + const char *context) +{ + char junk = 0; + int ch; + + if ((ch = attr_scan64_string(fp, str_buf, context)) < 0) + return (-1); + if (sscanf(STR(str_buf), "%lu%c", ptr, &junk) != 1 || junk != 0) { + msg_warn("malformed numerical data from %s while reading %s: %.100s", + VSTREAM_PATH(fp), context, STR(str_buf)); + return (-1); + } + return (ch); +} + +/* attr_vscan64 - receive attribute list from stream */ + +int attr_vscan64(VSTREAM *fp, int flags, va_list ap) +{ + const char *myname = "attr_scan64"; + static VSTRING *str_buf = 0; + static VSTRING *name_buf = 0; + int wanted_type = -1; + char *wanted_name; + unsigned int *number; + unsigned long *long_number; + VSTRING *string; + HTABLE *hash_table; + int ch; + int conversions; + + /* + * Sanity check. + */ + if (flags & ~ATTR_FLAG_ALL) + msg_panic("%s: bad flags: 0x%x", myname, flags); + + /* + * Initialize. + */ + if (str_buf == 0) { + str_buf = vstring_alloc(10); + name_buf = vstring_alloc(10); + } + + /* + * Iterate over all (type, name, value) triples. + */ + for (conversions = 0; /* void */ ; conversions++) { + + /* + * Determine the next attribute type and attribute name on the + * caller's wish list. + * + * If we're reading into a hash table, we already know that the + * attribute value is string-valued, and we get the attribute name + * from the input stream instead. This is secure only when the + * resulting table is queried with known to be good attribute names. + */ + if (wanted_type != ATTR_TYPE_HASH) { + wanted_type = va_arg(ap, int); + if (wanted_type == ATTR_TYPE_END) { + if ((flags & ATTR_FLAG_MORE) != 0) + return (conversions); + wanted_name = "(list terminator)"; + } else if (wanted_type == ATTR_TYPE_HASH) { + wanted_name = "(any attribute name or list terminator)"; + hash_table = va_arg(ap, HTABLE *); + if (va_arg(ap, int) !=ATTR_TYPE_END) + msg_panic("%s: ATTR_TYPE_HASH not followed by ATTR_TYPE_END", + myname); + } else { + wanted_name = va_arg(ap, char *); + } + } + + /* + * Locate the next attribute of interest in the input stream. + */ + for (;;) { + + /* + * Get the name of the next attribute. Hitting EOF is always bad. + * Hitting the end-of-input early is OK if the caller is prepared + * to deal with missing inputs. + */ + if (msg_verbose) + msg_info("%s: wanted attribute: %s", + VSTREAM_PATH(fp), wanted_name); + if ((ch = attr_scan64_string(fp, name_buf, + "input attribute name")) == VSTREAM_EOF) + return (-1); + if (ch == '\n' && LEN(name_buf) == 0) { + if (wanted_type == ATTR_TYPE_END + || wanted_type == ATTR_TYPE_HASH) + return (conversions); + if ((flags & ATTR_FLAG_MISSING) != 0) + msg_warn("missing attribute %s in input from %s", + wanted_name, VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * See if the caller asks for this attribute. + */ + if (wanted_type == ATTR_TYPE_HASH + || (wanted_type != ATTR_TYPE_END + && strcmp(wanted_name, STR(name_buf)) == 0)) + break; + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("spurious attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + + /* + * Skip over this attribute. The caller does not ask for it. + */ + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') + /* void */ ; + } + + /* + * Do the requested conversion. If the target attribute is a + * non-array type, disallow sending a multi-valued attribute, and + * disallow sending no value. If the target attribute is an array + * type, allow the sender to send a zero-element array (i.e. no value + * at all). XXX Need to impose a bound on the number of array + * elements. + */ + switch (wanted_type) { + case ATTR_TYPE_NUM: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + number = va_arg(ap, unsigned int *); + if ((ch = attr_scan64_number(fp, number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_LONG: + if (ch != ':') { + msg_warn("missing value for number attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + long_number = va_arg(ap, unsigned long *); + if ((ch = attr_scan64_long_number(fp, long_number, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_STR: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + string = va_arg(ap, VSTRING *); + if ((ch = attr_scan64_string(fp, string, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + break; + case ATTR_TYPE_HASH: + if (ch != ':') { + msg_warn("missing value for string attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if ((ch = attr_scan64_string(fp, str_buf, + "input attribute value")) < 0) + return (-1); + if (ch != '\n') { + msg_warn("multiple values for attribute %s from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (-1); + } + if (htable_locate(hash_table, STR(name_buf)) != 0) { + if ((flags & ATTR_FLAG_EXTRA) != 0) { + msg_warn("duplicate attribute %s in input from %s", + STR(name_buf), VSTREAM_PATH(fp)); + return (conversions); + } + } else { + htable_enter(hash_table, STR(name_buf), + mystrdup(STR(str_buf))); + } + break; + default: + msg_panic("%s: unknown type code: %d", myname, wanted_type); + } + } +} + +/* attr_scan64 - read attribute list from stream */ + +int attr_scan64(VSTREAM *fp, int flags,...) +{ + va_list ap; + int ret; + + va_start(ap, flags); + ret = attr_vscan64(fp, flags, ap); + va_end(ap); + return (ret); +} + +#ifdef TEST + + /* + * Proof of concept test program. Mirror image of the attr_scan64 test + * program. + */ +#include <msg_vstream.h> + +int var_line_limit = 2048; + +int main(int unused_argc, char **used_argv) +{ + VSTRING *str_val = vstring_alloc(1); + HTABLE *table = htable_create(1); + HTABLE_INFO **ht_info_list; + HTABLE_INFO **ht; + int int_val; + long long_val; + int ret; + + msg_verbose = 1; + msg_vstream_init(used_argv[0], VSTREAM_ERR); + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val, + ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, + ATTR_TYPE_STR, ATTR_NAME_STR, str_val, + ATTR_TYPE_HASH, table, + ATTR_TYPE_END)) > 3) { + vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); + myfree((char *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if ((ret = attr_scan64(VSTREAM_IN, + ATTR_FLAG_STRICT, + ATTR_TYPE_NUM, ATTR_NAME_NUM, &int_val, + ATTR_TYPE_LONG, ATTR_NAME_LONG, &long_val, + ATTR_TYPE_STR, ATTR_NAME_STR, str_val, + ATTR_TYPE_END)) == 3) { + vstream_printf("%s %d\n", ATTR_NAME_NUM, int_val); + vstream_printf("%s %ld\n", ATTR_NAME_LONG, long_val); + vstream_printf("%s %s\n", ATTR_NAME_STR, STR(str_val)); + ht_info_list = htable_list(table); + for (ht = ht_info_list; *ht; ht++) + vstream_printf("(hash) %s %s\n", ht[0]->key, ht[0]->value); + myfree((char *) ht_info_list); + } else { + vstream_printf("return: %d\n", ret); + } + if (vstream_fflush(VSTREAM_OUT) != 0) + msg_fatal("write error: %m"); + + vstring_free(str_val); + htable_free(table, myfree); + + return (0); +} + +#endif diff --git a/gnu/dist/postfix/src/util/attr_scan64.ref b/gnu/dist/postfix/src/util/attr_scan64.ref new file mode 100644 index 00000000000..1b3a01581cf --- /dev/null +++ b/gnu/dist/postfix/src/util/attr_scan64.ref @@ -0,0 +1,46 @@ +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_print64: send attr name foo-name value foo-value +./attr_print64: send attr name bar-name value bar-value +./attr_print64: send attr number = 4711 +./attr_print64: send attr long_number = 1234 +./attr_print64: send attr string = whoopee +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan64: input attribute name: foo-name +./attr_scan64: input attribute value: foo-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan64: input attribute name: bar-name +./attr_scan64: input attribute value: bar-value +./attr_scan64: unknown_stream: wanted attribute: (any attribute name or list terminator) +./attr_scan64: input attribute name: (end) +./attr_scan64: unknown_stream: wanted attribute: number +./attr_scan64: input attribute name: number +./attr_scan64: input attribute value: 4711 +./attr_scan64: unknown_stream: wanted attribute: long_number +./attr_scan64: input attribute name: long_number +./attr_scan64: input attribute value: 1234 +./attr_scan64: unknown_stream: wanted attribute: string +./attr_scan64: input attribute name: string +./attr_scan64: input attribute value: whoopee +./attr_scan64: unknown_stream: wanted attribute: (list terminator) +./attr_scan64: input attribute name: (end) +number 4711 +long_number 1234 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value +number 4711 +long_number 1234 +string whoopee +(hash) foo-name foo-value +(hash) bar-name bar-value diff --git a/gnu/dist/postfix/src/util/base64_code.c b/gnu/dist/postfix/src/util/base64_code.c new file mode 100644 index 00000000000..21210e6053d --- /dev/null +++ b/gnu/dist/postfix/src/util/base64_code.c @@ -0,0 +1,201 @@ +/*++ +/* NAME +/* base64_code 3 +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include <base64_code.h> +/* +/* VSTRING *base64_encode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* int len; +/* +/* VSTRING *base64_decode(result, in, len) +/* VSTRING *result; +/* const char *in; +/* int len; +/* DESCRIPTION +/* base64_encode() takes a block of len bytes and encodes it as one +/* null-terminated string. The result value is the result argument. +/* +/* base64_decode() performs the opposite transformation. The result +/* value is the result argument. The result is null terminated, whether +/* or not that makes sense. +/* DIAGNOSTICS +/* base64_decode () returns a null pointer when the input contains +/* characters not in the base 64 alphabet. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <vstring.h> +#include <base64_code.h> + +/* Application-specific. */ + +static unsigned char to_b64[] = +"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define UNSIG_CHAR_PTR(x) ((unsigned char *)(x)) + +/* base64_encode - raw data to encoded */ + +VSTRING *base64_encode(VSTRING *result, const char *in, int len) +{ + const unsigned char *cp; + int count; + + /* + * Encode 3 -> 4. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = len; count > 0; count -= 3, cp += 3) { + VSTRING_ADDCH(result, to_b64[cp[0] >> 2]); + if (count > 1) { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4 | cp[1] >> 4]); + if (count > 2) { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2 | cp[2] >> 6]); + VSTRING_ADDCH(result, to_b64[cp[2] & 0x3f]); + } else { + VSTRING_ADDCH(result, to_b64[(cp[1] & 0xf) << 2]); + VSTRING_ADDCH(result, '='); + break; + } + } else { + VSTRING_ADDCH(result, to_b64[(cp[0] & 0x3) << 4]); + VSTRING_ADDCH(result, '='); + VSTRING_ADDCH(result, '='); + break; + } + } + VSTRING_TERMINATE(result); + return (result); +} + +/* base64_decode - encoded data to raw */ + +VSTRING *base64_decode(VSTRING *result, const char *in, int len) +{ + static unsigned char *un_b64 = 0; + const unsigned char *cp; + int count; + int ch0; + int ch1; + int ch2; + int ch3; + +#define CHARS_PER_BYTE 256 +#define INVALID 0xff + + /* + * Sanity check. + */ + if (len % 4) + return (0); + + /* + * Once: initialize the decoding lookup table on the fly. + */ + if (un_b64 == 0) { + un_b64 = (unsigned char *) mymalloc(CHARS_PER_BYTE); + memset(un_b64, INVALID, CHARS_PER_BYTE); + for (cp = to_b64; cp < to_b64 + sizeof(to_b64); cp++) + un_b64[*cp] = cp - to_b64; + } + + /* + * Decode 4 -> 3. + */ + VSTRING_RESET(result); + for (cp = UNSIG_CHAR_PTR(in), count = 0; count < len; count += 4) { + if ((ch0 = un_b64[*cp++]) == INVALID + || (ch1 = un_b64[*cp++]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch0 << 2 | ch1 >> 4); + if ((ch2 = *cp++) == '=') + break; + if ((ch2 = un_b64[ch2]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch1 << 4 | ch2 >> 2); + if ((ch3 = *cp++) == '=') + break; + if ((ch3 = un_b64[ch3]) == INVALID) + return (0); + VSTRING_ADDCH(result, ch2 << 6 | ch3); + } + VSTRING_TERMINATE(result); + return (result); +} + +#ifdef TEST + + /* + * Proof-of-concept test program: convert to base 64 and back. + */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +int main(int unused_argc, char **unused_argv) +{ + VSTRING *b1 = vstring_alloc(1); + VSTRING *b2 = vstring_alloc(1); + char *test = "this is a test"; + +#define DECODE(b,x,l) { \ + if (base64_decode((b),(x),(l)) == 0) \ + msg_panic("bad base64: %s", (x)); \ + } +#define VERIFY(b,t) { \ + if (strcmp((b), (t)) != 0) \ + msg_panic("bad test: %s", (b)); \ + } + + base64_encode(b1, test, strlen(test)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + base64_encode(b1, test, strlen(test)); + base64_encode(b2, STR(b1), LEN(b1)); + base64_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + base64_encode(b1, test, strlen(test)); + base64_encode(b2, STR(b1), LEN(b1)); + base64_encode(b1, STR(b2), LEN(b2)); + base64_encode(b2, STR(b1), LEN(b1)); + base64_encode(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + DECODE(b1, STR(b2), LEN(b2)); + DECODE(b2, STR(b1), LEN(b1)); + VERIFY(STR(b2), test); + + vstring_free(b1); + vstring_free(b2); + return (0); +} + +#endif diff --git a/gnu/dist/postfix/src/util/base64_code.h b/gnu/dist/postfix/src/util/base64_code.h new file mode 100644 index 00000000000..d018947e1c1 --- /dev/null +++ b/gnu/dist/postfix/src/util/base64_code.h @@ -0,0 +1,36 @@ +#ifndef _BASE64_CODE_H_INCLUDED_ +#define _BASE64_CODE_H_INCLUDED_ + +/*++ +/* NAME +/* base64_code 3h +/* SUMMARY +/* encode/decode data, base 64 style +/* SYNOPSIS +/* #include <base64_code.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> + + /* + * External interface. + */ +extern VSTRING *base64_encode(VSTRING *, const char *, int); +extern VSTRING *base64_decode(VSTRING *, const char *, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/util/cache.in b/gnu/dist/postfix/src/util/cache.in new file mode 100644 index 00000000000..89974bd2c66 --- /dev/null +++ b/gnu/dist/postfix/src/util/cache.in @@ -0,0 +1,26 @@ +a +1 +b +2 +c +3 +d +4 +e +5 +f +6 +f +e +d +c +b +a +1 +b +c +d +e +f +6 +f diff --git a/gnu/dist/postfix/src/util/dict.c b/gnu/dist/postfix/src/util/dict.c index 7d07d3be0be..5fd30887983 100644 --- a/gnu/dist/postfix/src/util/dict.c +++ b/gnu/dist/postfix/src/util/dict.c @@ -182,6 +182,7 @@ #include "vstring.h" #include "readlline.h" #include "mac_parse.h" +#include "stringops.h" #include "dict.h" #include "dict_ht.h" @@ -372,43 +373,18 @@ void dict_load_file(const char *dict_name, const char *path) void dict_load_fp(const char *dict_name, VSTREAM *fp) { VSTRING *buf; - char *start; char *member; char *val; - char *cp; - char *ep; int lineno; - - /* - * Ugly macros to make complex expressions less unreadable. - */ -#define SKIP(start, var, cond) \ - for (var = start; *var && (cond); var++); - -#define TRIM(s) { \ - char *p; \ - for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--); \ - *p = 0; \ - } + const char *err; buf = vstring_alloc(100); lineno = 0; - while (readlline(buf, fp, &lineno, READLL_STRIPNL)) { - start = STR(buf); - SKIP(start, member, ISSPACE(*member)); /* find member begin */ - if (*member == 0 || *member == '#') - continue; /* comment or blank line */ - SKIP(member, ep, !ISSPACE(*ep) && *ep != '='); /* find member end */ - SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */ - if (*cp && *cp != '=') /* need '=' or end of string */ - msg_fatal("%s, line %d: whitespace in attribute name: \"%s\"", - VSTREAM_PATH(fp), lineno, member); - if (*cp) - cp++; /* skip over '=' */ - *ep = 0; /* terminate member name */ - SKIP(cp, val, ISSPACE(*val)); /* skip leading blanks */ - TRIM(val); /* trim trailing blanks */ + while (readlline(buf, fp, &lineno)) { + if ((err = split_nameval(STR(buf), &member, &val)) != 0) + msg_fatal("%s, line %d: %s: \"%s\"", + VSTREAM_PATH(fp), lineno, err, STR(buf)); dict_update(dict_name, member, val); } vstring_free(buf); diff --git a/gnu/dist/postfix/src/util/dict.h b/gnu/dist/postfix/src/util/dict.h index 5a669aa096d..1d9d8c4a129 100644 --- a/gnu/dist/postfix/src/util/dict.h +++ b/gnu/dist/postfix/src/util/dict.h @@ -55,6 +55,7 @@ extern DICT *dict_debug(DICT *); #define DICT_FLAG_DUP_REPLACE (1<<7) /* if file, replace dups */ #define DICT_FLAG_SYNC_UPDATE (1<<8) /* if file, sync updates */ #define DICT_FLAG_DEBUG (1<<9) /* log access */ +#define DICT_FLAG_FOLD_KEY (1<<10) /* lowercase the lookup key */ extern int dict_unknown_allowed; extern int dict_errno; diff --git a/gnu/dist/postfix/src/util/dict_db.c b/gnu/dist/postfix/src/util/dict_db.c index 69518e10f8a..423244a3a7f 100644 --- a/gnu/dist/postfix/src/util/dict_db.c +++ b/gnu/dist/postfix/src/util/dict_db.c @@ -437,6 +437,23 @@ static DICT *dict_db_open(const char *class, const char *path, int open_flags, #endif + /* + * Mismatches between #include file and library are a common cause for + * trouble. + */ +#if DB_VERSION_MAJOR > 1 + int major_version; + int minor_version; + int patch_version; + + (void) db_version(&major_version, &minor_version, &patch_version); + if (major_version != DB_VERSION_MAJOR) + msg_fatal("incorrect version of Berkeley DB: " + "compiled against %d.%d.%d, linked against %d.%d.%d", + DB_VERSION_MAJOR, DB_VERSION_MINOR, DB_VERSION_PATCH, + major_version, minor_version, patch_version); +#endif + db_path = concatenate(path, ".db", (char *) 0); /* diff --git a/gnu/dist/postfix/src/util/dict_ldap.c b/gnu/dist/postfix/src/util/dict_ldap.c index ebbfe8f9003..2cf8936c43e 100644 --- a/gnu/dist/postfix/src/util/dict_ldap.c +++ b/gnu/dist/postfix/src/util/dict_ldap.c @@ -62,6 +62,9 @@ /* The cache size in bytes. Does nothing if the cache is off, of course. /* .IP \fIldapsource_\fRdereference /* How to handle LDAP aliases. See ldap.h or ldap_open(3) man page. +/* .IP \fIldapsource_\fRdebuglevel +/* Debug level. See 'loglevel' option in slapd.conf(5) man page. +/* Currently only in openldap libraries (and derivatives). /* SEE ALSO /* dict(3) generic dictionary manager /* AUTHOR(S) @@ -77,6 +80,9 @@ /* John Hensley /* john@sunislelodge.com /* +/* LaMont Jones +/* lamont@hp.com +/* /*--*/ /* System library. */ @@ -94,6 +100,14 @@ #include <ldap.h> #include <string.h> +/* Handle differences between LDAP SDK's constant definitions */ +#ifndef LDAP_CONST +#define LDAP_CONST const +#endif +#ifndef LDAP_OPT_SUCCESS +#define LDAP_OPT_SUCCESS 0 +#endif + /* Utility library. */ #include "match_list.h" @@ -131,6 +145,7 @@ typedef struct { long cache_expiry; long cache_size; int dereference; + int debuglevel; LDAP *ld; } DICT_LDAP; @@ -139,6 +154,13 @@ typedef struct { */ static jmp_buf env; +static void dict_ldap_logprint(LDAP_CONST char *data) +{ + char *myname = "dict_ldap_debug"; + + msg_info("%s: %s", myname, data); +} + static void dict_ldap_timeout(int unused_sig) { longjmp(env, 1); @@ -153,6 +175,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) #ifdef LDAP_API_FEATURE_X_MEMCACHE LDAPMemCache *dircache; + #endif #ifdef LDAP_OPT_NETWORK_TIMEOUT @@ -170,14 +193,16 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) dict_ldap->ld = ldap_init(dict_ldap->server_host, (int) dict_ldap->server_port); if (dict_ldap->ld == NULL) { - msg_warn("%s: Unable to int LDAP server %s", + msg_warn("%s: Unable to init LDAP server %s", myname, dict_ldap->server_host); dict_errno = DICT_ERR_RETRY; return (-1); } mytimeval.tv_sec = dict_ldap->timeout; mytimeval.tv_usec = 0; - ldap_set_option(dict_ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &mytimeval); + if (ldap_set_option(dict_ldap->ld, LDAP_OPT_NETWORK_TIMEOUT, &mytimeval) != + LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to set network timeout.", myname); #else if ((saved_alarm = signal(SIGALRM, dict_ldap_timeout)) == SIG_ERR) { msg_warn("%s: Error setting signal handler for open timeout: %m", @@ -212,11 +237,24 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) * Mattice for this, and to Hery Rakotoarisoa for the v3 update. */ #if (LDAP_API_VERSION >= 2000) - ldap_set_option(dict_ldap->ld, LDAP_OPT_DEREF, &(dict_ldap->dereference)); + if (ldap_set_option(dict_ldap->ld, LDAP_OPT_DEREF, + &(dict_ldap->dereference)) != LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to set dereference option.", myname); #else dict_ldap->ld->ld_deref = dict_ldap->dereference; #endif +#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN) + if (dict_ldap->debuglevel > 0 && + ber_set_option(NULL, LBER_OPT_LOG_PRINT_FN, + (LDAP_CONST *) dict_ldap_logprint) != LBER_OPT_SUCCESS) + msg_warn("%s: Unable to set ber logprint function.", myname); + if (ldap_set_option(dict_ldap->ld, LDAP_OPT_DEBUG_LEVEL, + &(dict_ldap->debuglevel)) != LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to set LDAP debug level.", myname); +#endif + + /* * If this server requires a bind, do so. Thanks to Sam Tardieu for * noticing that the original bind call was broken. @@ -267,7 +305,7 @@ static int dict_ldap_connect(DICT_LDAP *dict_ldap) } else { if (msg_verbose) msg_info("%s: Caching enabled for %s", - myname, dict_ldap->ldapsource); + myname, dict_ldap->ldapsource); } } #else @@ -342,7 +380,7 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, if (strcasecmp(dict_ldap->result_attributes->argv[i], attr) == 0) { if (msg_verbose) - msg_info("%s: search returned %d value(s) for requested result attribute %s", myname, i, attr); + msg_info("%s: search returned %ld value(s) for requested result attribute %s", myname, i, attr); break; } } @@ -374,21 +412,23 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage * res, 0, &tv, &resloop); } switch (rc) { - case LDAP_SUCCESS: - dict_ldap_get_values(dict_ldap, resloop, result); - break; - case LDAP_NO_SUCH_OBJECT: - /* Go ahead and treat this as though the DN existed - * and just didn't have any result attributes. - */ - msg_warn("%s: DN %s not found, skipping ", myname, - vals[i]); - break; - default: - msg_warn("%s: search error %d: %s ", myname, rc, + case LDAP_SUCCESS: + dict_ldap_get_values(dict_ldap, resloop, result); + break; + case LDAP_NO_SUCH_OBJECT: + + /* + * Go ahead and treat this as though the DN existed + * and just didn't have any result attributes. + */ + msg_warn("%s: DN %s not found, skipping ", myname, + vals[i]); + break; + default: + msg_warn("%s: search error %d: %s ", myname, rc, ldap_err2string(rc)); - dict_errno = DICT_ERR_RETRY; - break; + dict_errno = DICT_ERR_RETRY; + break; } if (resloop != 0) @@ -428,11 +468,12 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) * load on the LDAP server. */ if (dict_ldap->domain) { - const char *p=strrchr(name,'@'); + const char *p = strrchr(name, '@'); + if (p != 0) - p=p+1; + p = p + 1; else - p=name; + p = name; if (match_list_match(dict_ldap->domain, p) == 0) { if (msg_verbose) msg_info("%s: domain of %s not found in domain list", myname, @@ -454,7 +495,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) if (dict_ldap->ld == NULL) { if (msg_verbose) msg_info - ("%s: No existing connection for ldapsource %s, reopening", + ("%s: No existing connection for LDAP source %s, reopening", myname, dict_ldap->ldapsource); dict_ldap_connect(dict_ldap); @@ -465,7 +506,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) if (dict_errno) return (0); } else if (msg_verbose) - msg_info("%s: Using existing connection for ldapsource %s", + msg_info("%s: Using existing connection for LDAP source %s", myname, dict_ldap->ldapsource); @@ -540,31 +581,32 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) /* * Make sure it's %[sud] and not something else. For backward - * compatibilty, treat anything other than %u or %d as %s, with - * a warning. + * compatibilty, treat anything other than %u or %d as %s, with a + * warning. */ if (*(sub) == '%') { - char *u=vstring_str(escaped_name); - char *p=strchr(u,'@'); - switch (*(sub+1)) { - case 'd': - if (p) - vstring_strcat(filter_buf, p+1); - break; - case 'u': - if (p) - vstring_strncat(filter_buf, u, p-u); - else - vstring_strcat(filter_buf, u); - break; - default: - msg_warn - ("%s: Invalid lookup substitution format '%%%c'!", - myname, *(sub + 1)); - /* fall through */ - case 's': + char *u = vstring_str(escaped_name); + char *p = strchr(u, '@'); + + switch (*(sub + 1)) { + case 'd': + if (p) + vstring_strcat(filter_buf, p + 1); + break; + case 'u': + if (p) + vstring_strncat(filter_buf, u, p - u); + else vstring_strcat(filter_buf, u); - break; + break; + default: + msg_warn + ("%s: Invalid lookup substitution format '%%%c'!", + myname, *(sub + 1)); + /* fall through */ + case 's': + vstring_strcat(filter_buf, u); + break; } sub++; } else @@ -580,11 +622,35 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) msg_info("%s: Searching with filter %s", myname, vstring_str(filter_buf)); - if ((rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base, - dict_ldap->scope, - vstring_str(filter_buf), - dict_ldap->result_attributes->argv, - 0, &tv, &res)) == LDAP_SUCCESS) { + rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base, + dict_ldap->scope, + vstring_str(filter_buf), + dict_ldap->result_attributes->argv, + 0, &tv, &res); + + if (rc == LDAP_SERVER_DOWN) { + if (msg_verbose) + msg_info("%s: Lost connection for LDAP source %s, reopening", + myname, dict_ldap->ldapsource); + + ldap_unbind(dict_ldap->ld); + dict_ldap->ld = NULL; + dict_ldap_connect(dict_ldap); + + /* + * if dict_ldap_connect() set dict_errno, abort. + */ + if (dict_errno) + return (0); + + rc = ldap_search_st(dict_ldap->ld, dict_ldap->search_base, + dict_ldap->scope, + vstring_str(filter_buf), + dict_ldap->result_attributes->argv, + 0, &tv, &res); + + } + if (rc == LDAP_SUCCESS) { /* * Search worked; extract the requested result_attribute. @@ -598,7 +664,9 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) */ #if (LDAP_API_VERSION >= 2000) - ldap_get_option(dict_ldap->ld, LDAP_OPT_ERROR_NUMBER, &rc); + if (ldap_get_option(dict_ldap->ld, LDAP_OPT_ERROR_NUMBER, &rc) != + LDAP_OPT_SUCCESS) + msg_warn("%s: Unable to get last error number.", myname); if (rc != LDAP_SUCCESS && rc != LDAP_DECODING_ERROR) msg_warn("%s: Had some trouble with entries returned by search: %s", myname, ldap_err2string(rc)); #else @@ -641,7 +709,7 @@ static const char *dict_ldap_lookup(DICT *dict, const char *name) ldap_msgfree(res); if (filter_buf != 0) vstring_free(filter_buf); - if (escaped_name != 0) + if (escaped_name != 0) vstring_free(escaped_name); /* @@ -759,7 +827,12 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) mystrdup((char *) get_mail_conf_str(vstring_str(config_param), "", 0, 0)); if (*domainlist) { +#ifdef MATCH_FLAG_NONE + dict_ldap->domain = match_list_init(MATCH_FLAG_NONE, + domainlist, 1, match_string); +#else dict_ldap->domain = match_list_init(domainlist, 1, match_string); +#endif if (dict_ldap->domain == NULL) msg_warn("%s: domain match list creation using \"%s\" failed, will continue without it", myname, domainlist); if (msg_verbose) @@ -892,6 +965,18 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) msg_info("%s: %s is %d", myname, vstring_str(config_param), dict_ldap->dereference); + /* + * Debug level. + */ +#if defined(LDAP_OPT_DEBUG_LEVEL) && defined(LBER_OPT_LOG_PRINT_FN) + vstring_sprintf(config_param, "%s_debuglevel", ldapsource); + dict_ldap->debuglevel = get_mail_conf_int(vstring_str(config_param), 0, 0, + 0); + if (msg_verbose) + msg_info("%s: %s is %d", myname, vstring_str(config_param), + dict_ldap->debuglevel); +#endif + dict_ldap_connect(dict_ldap); /* @@ -908,7 +993,7 @@ DICT *dict_ldap_open(const char *ldapsource, int dummy, int dict_flags) /* * Otherwise, we're all set. Return the new dict_ldap structure. */ - return (DICT_DEBUG(&dict_ldap->dict)); + return (DICT_DEBUG (&dict_ldap->dict)); } #endif diff --git a/gnu/dist/postfix/src/util/dict_mysql.c b/gnu/dist/postfix/src/util/dict_mysql.c index 28fc7015639..f778e928d08 100644 --- a/gnu/dist/postfix/src/util/dict_mysql.c +++ b/gnu/dist/postfix/src/util/dict_mysql.c @@ -274,14 +274,14 @@ static MYSQL_RES *plmysql_query(PLMYSQL *PLDB, if (res == 0 && host->stat == STATACTIVE) { if (!(mysql_query(host->db, query))) { if ((res = mysql_store_result(host->db)) == 0) { - msg_warn("%s", mysql_error(host->db)); + msg_warn("mysql query failed: %s", mysql_error(host->db)); plmysql_down_host(host); } else { if (msg_verbose) msg_info("dict_mysql: successful query from host %s", host->hostname); } } else { - msg_warn("%s", mysql_error(host->db)); + msg_warn("mysql query failed: %s", mysql_error(host->db)); plmysql_down_host(host); } } @@ -323,7 +323,8 @@ static void plmysql_connect_single(HOST *host, char *dbname, char *username, cha host->hostname); host->stat = STATACTIVE; } else { - msg_warn("%s", mysql_error(host->db)); + msg_warn("connect to mysql server %s: %s", + host->hostname, mysql_error(host->db)); plmysql_down_host(host); } if (hostname) @@ -366,7 +367,7 @@ DICT *dict_mysql_open(const char *name, int unused_open_flags, int dict_flags) if (dict_mysql->pldb == NULL) msg_fatal("couldn't intialize pldb!\n"); dict_register(name, (DICT *) dict_mysql); - return (DICT_DEBUG(&dict_mysql->dict)); + return (DICT_DEBUG (&dict_mysql->dict)); } /* mysqlname_parse - parse mysql configuration file */ diff --git a/gnu/dist/postfix/src/util/dict_open.c b/gnu/dist/postfix/src/util/dict_open.c index 2c15ac897ef..52a5dddc14b 100644 --- a/gnu/dist/postfix/src/util/dict_open.c +++ b/gnu/dist/postfix/src/util/dict_open.c @@ -81,6 +81,8 @@ /* .IP DICT_FLAG_SYNC_UPDATE /* With file-based maps, flush I/O buffers to file after each update. /* Thus feature is not supported with some file-based dictionaries. +/* .IP DICT_FLAG_FOLD_KEY +/* Fold the lookup key to lower case. /* .PP /* The dictionary types are as follows: /* .IP environ @@ -166,6 +168,7 @@ #include <dict_mysql.h> #include <dict_pcre.h> #include <dict_regexp.h> +#include <dict_static.h> #include <stringops.h> #include <split_at.h> #include <htable.h> @@ -212,6 +215,7 @@ static DICT_OPEN_INFO dict_open_info[] = { #ifdef HAS_POSIX_REGEXP DICT_TYPE_REGEXP, dict_regexp_open, #endif + DICT_TYPE_STATIC, dict_static_open, 0, }; @@ -339,6 +343,7 @@ static NORETURN usage(char *myname) int main(int argc, char **argv) { VSTRING *keybuf = vstring_alloc(1); + VSTRING *inbuf = vstring_alloc(1); DICT *dict; char *dict_name; int open_flags; @@ -374,13 +379,16 @@ int main(int argc, char **argv) dict_name = argv[optind]; dict = dict_open(dict_name, open_flags, DICT_FLAG_LOCK); dict_register(dict_name, dict); - while (vstring_fgets_nonl(keybuf, VSTREAM_IN)) { - bufp = vstring_str(keybuf); - if ((cmd = mystrtok(&bufp, " ")) == 0) + while (vstring_fgets_nonl(inbuf, VSTREAM_IN)) { + bufp = vstring_str(inbuf); + if ((cmd = mystrtok(&bufp, " ")) == 0 || *bufp == 0) { + vstream_printf("usage: del key|get key|put key=value\n"); + vstream_fflush(VSTREAM_OUT); continue; + } if (dict_changed()) msg_warn("dictionary has changed"); - key = mystrtok(&bufp, " ="); + key = vstring_str(unescape(keybuf, mystrtok(&bufp, " ="))); value = mystrtok(&bufp, " ="); if (strcmp(cmd, "del") == 0 && key && !value) { if (dict_del(dict, key)) @@ -404,6 +412,7 @@ int main(int argc, char **argv) vstream_fflush(VSTREAM_OUT); } vstring_free(keybuf); + vstring_free(inbuf); dict_close(dict); return (0); } diff --git a/gnu/dist/postfix/src/util/dict_pcre.c b/gnu/dist/postfix/src/util/dict_pcre.c index 6d3c6f52393..1cc0f19f946 100644 --- a/gnu/dist/postfix/src/util/dict_pcre.c +++ b/gnu/dist/postfix/src/util/dict_pcre.c @@ -257,13 +257,7 @@ DICT *dict_pcre_open(const char *map, int unused_flags, int dict_flags) if ((map_fp = vstream_fopen(map, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", map); } - while (readlline(line_buffer, map_fp, &lineno, READLL_STRIPNL)) { - - if (*vstring_str(line_buffer) == '#') /* Skip comments */ - continue; - - if (*vstring_str(line_buffer) == 0) /* Skip blank lines */ - continue; + while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); trimblanks(p, 0)[0] = 0; /* Trim space at end */ diff --git a/gnu/dist/postfix/src/util/dict_regexp.c b/gnu/dist/postfix/src/util/dict_regexp.c index 1debbe96ff2..732208ca67d 100644 --- a/gnu/dist/postfix/src/util/dict_regexp.c +++ b/gnu/dist/postfix/src/util/dict_regexp.c @@ -356,15 +356,9 @@ DICT *dict_regexp_open(const char *map, int unused_flags, int dict_flags) if ((map_fp = vstream_fopen(map, O_RDONLY, 0)) == 0) { msg_fatal("open %s: %m", map); } - while (readlline(line_buffer, map_fp, &lineno, READLL_STRIPNL)) { + while (readlline(line_buffer, map_fp, &lineno)) { p = vstring_str(line_buffer); - if (*p == '#') /* Skip comments */ - continue; - - if (*p == 0) /* Skip blank lines */ - continue; - trimblanks(p, 0)[0] = 0; /* Trim space at end */ rule = dict_regexp_parseline(lineno, p, &nsub, map_fp); diff --git a/gnu/dist/postfix/src/util/dict_static.c b/gnu/dist/postfix/src/util/dict_static.c new file mode 100644 index 00000000000..1b06a89aba4 --- /dev/null +++ b/gnu/dist/postfix/src/util/dict_static.c @@ -0,0 +1,72 @@ +/*++ +/* NAME +/* dict_static 3 +/* SUMMARY +/* dictionary manager interface to static variables +/* SYNOPSIS +/* #include <dict_static.h> +/* +/* DICT *dict_static_open(name, dummy, dict_flags) +/* const char *name; +/* int dummy; +/* int dict_flags; +/* DESCRIPTION +/* dict_static_open() implements a dummy dictionary that returns +/* as lookup result the dictionary name, regardless of the lookup +/* key value. +/* +/* The \fIdummy\fR argument is ignored. +/* SEE ALSO +/* dict(3) generic dictionary manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <stdio.h> /* sprintf() prototype */ +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +/* Utility library. */ + +#include "mymalloc.h" +#include "msg.h" +#include "dict.h" +#include "dict_static.h" + +/* dict_static_lookup - access static value*/ + +static const char *dict_static_lookup(DICT *dict, const char *name) +{ + dict_errno = 0; + + return (dict->name); +} + +/* dict_static_close - close static dictionary */ + +static void dict_static_close(DICT *dict) +{ + dict_free(dict); +} + +/* dict_static_open - make association with static variable */ + +DICT *dict_static_open(const char *name, int unused_flags, int dict_flags) +{ + DICT *dict; + + dict = dict_alloc(DICT_TYPE_STATIC, name, sizeof(*dict)); + dict->lookup = dict_static_lookup; + dict->close = dict_static_close; + dict->flags = dict_flags | DICT_FLAG_FIXED; + return (DICT_DEBUG (dict)); +} diff --git a/gnu/dist/postfix/src/util/dict_static.h b/gnu/dist/postfix/src/util/dict_static.h new file mode 100644 index 00000000000..d4ad1cc11d3 --- /dev/null +++ b/gnu/dist/postfix/src/util/dict_static.h @@ -0,0 +1,35 @@ +#ifndef _DICT_STATIC_H_INCLUDED_ +#define _DICT_STATIC_H_INCLUDED_ + +/*++ +/* NAME +/* dict_static 3h +/* SUMMARY +/* dictionary manager interface to static settings +/* SYNOPSIS +/* #include <dict_static.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <dict.h> + + /* + * External interface. + */ +#define DICT_TYPE_STATIC "static" + +extern DICT *dict_static_open(const char *, int, int); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* jeffm +/* ghostgun.com +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/util/dir_forest.c b/gnu/dist/postfix/src/util/dir_forest.c index 289cd122e10..929f937c730 100644 --- a/gnu/dist/postfix/src/util/dir_forest.c +++ b/gnu/dist/postfix/src/util/dir_forest.c @@ -104,7 +104,7 @@ char *dir_forest(VSTRING *buf, const char *path, int depth) } VSTRING_TERMINATE(buf); - if (msg_verbose) + if (msg_verbose > 1) msg_info("%s: %s -> %s", myname, path, vstring_str(buf)); return (vstring_str(buf)); } diff --git a/gnu/dist/postfix/src/util/events.c b/gnu/dist/postfix/src/util/events.c index c07504a26d6..99e1269565c 100644 --- a/gnu/dist/postfix/src/util/events.c +++ b/gnu/dist/postfix/src/util/events.c @@ -32,6 +32,8 @@ /* /* void event_disable_readwrite(fd) /* int fd; +/* +/* void event_drain() /* DESCRIPTION /* This module delivers I/O and timer events. /* Multiple I/O streams and timers can be monitored simultaneously. @@ -98,6 +100,10 @@ /* event_disable_readwrite() disables further I/O events on the specified /* I/O channel. The application is allowed to cancel non-existing /* I/O event requests. +/* +/* event_drain() repeatedly calls event_loop() until no more timer +/* events or I/O events are pending. This routine must not be called +/* from an event_whatever() callback routine. /* DIAGNOSTICS /* Panics: interface violations. Fatal errors: out of memory, /* system call failure. Warnings: the number of available @@ -242,6 +248,21 @@ time_t event_time(void) return (event_present); } +/* event_drain - loop until all pending events are done */ + +void event_drain(void) +{ + fd_set zero_mask; + + if (EVENT_INIT_NEEDED()) + return; + + FD_ZERO(&zero_mask); + while (event_timer_head.pred != event_timer_head.succ + || memcmp(&zero_mask, &event_xmask, sizeof(zero_mask)) != 0) + event_loop(-1); +} + /* event_enable_read - enable read events */ void event_enable_read(int fd, EVENT_NOTIFY_RDWR callback, char *context) diff --git a/gnu/dist/postfix/src/util/events.h b/gnu/dist/postfix/src/util/events.h index bc39b4787c4..d7235093b21 100644 --- a/gnu/dist/postfix/src/util/events.h +++ b/gnu/dist/postfix/src/util/events.h @@ -29,6 +29,7 @@ extern void event_disable_readwrite(int); extern time_t event_request_timer(EVENT_NOTIFY_TIME, char *, int); extern int event_cancel_timer(EVENT_NOTIFY_TIME, char *); extern void event_loop(int); +extern void event_drain(void); /* * Event codes. diff --git a/gnu/dist/postfix/src/util/fifo_trigger.c b/gnu/dist/postfix/src/util/fifo_trigger.c index b2801143610..841011a1bfc 100644 --- a/gnu/dist/postfix/src/util/fifo_trigger.c +++ b/gnu/dist/postfix/src/util/fifo_trigger.c @@ -13,7 +13,8 @@ /* int timeout; /* DESCRIPTION /* fifo_trigger() wakes up the named fifo server by writing -/* the contents of the specified buffer to the fifo. +/* the contents of the specified buffer to the fifo. There is +/* no guarantee that the written data will actually be received. /* /* Arguments: /* .IP service @@ -26,7 +27,7 @@ /* Deadline in seconds. Specify a value <= 0 to disable /* the time limit. /* DIAGNOSTICS -/* The result is zero in case of success, -1 in case of problems. +/* The result is zero when the fifo could be opened, -1 otherwise. /* BUGS /* LICENSE /* .ad @@ -49,25 +50,37 @@ #include <msg.h> #include <iostuff.h> +#include <safe_open.h> #include <trigger.h> /* fifo_trigger - wakeup fifo server */ int fifo_trigger(const char *service, const char *buf, int len, int timeout) { + static VSTRING *why; char *myname = "fifo_trigger"; + VSTREAM *fp; int fd; + if (why == 0) + why = vstring_alloc(1); + /* * Write the request to the service fifo. According to POSIX, the open * shall always return immediately, and shall return an error when no * process is reading from the FIFO. + * + * Use safe_open() so that we don't follow symlinks, and so that we don't + * open files with multiple hard links. We're not (yet) going to bother + * the caller with safe_open() specific quirks such as the why argument. */ - if ((fd = open(service, O_WRONLY | O_NONBLOCK, 0)) < 0) { + if ((fp = safe_open(service, O_WRONLY | O_NONBLOCK, 0, + (struct stat *) 0, -1, -1, why)) == 0) { if (msg_verbose) - msg_info("%s: open %s: %m", myname, service); + msg_info("%s: open %s: %s", myname, service, vstring_str(why)); return (-1); } + fd = vstream_fileno(fp); /* * Write the request... @@ -80,7 +93,7 @@ int fifo_trigger(const char *service, const char *buf, int len, int timeout) /* * Disconnect. */ - if (close(fd)) + if (vstream_fclose(fp)) if (msg_verbose) msg_warn("%s: close %s: %m", myname, service); return (0); diff --git a/gnu/dist/postfix/src/util/inet_trigger.c b/gnu/dist/postfix/src/util/inet_trigger.c index e92c2d8dbfb..dddbcb62aac 100644 --- a/gnu/dist/postfix/src/util/inet_trigger.c +++ b/gnu/dist/postfix/src/util/inet_trigger.c @@ -16,6 +16,10 @@ /* a brief connection to it and by writing the contents of the /* named buffer. /* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* /* Arguments: /* .IP service /* Name of the communication endpoint. @@ -45,6 +49,7 @@ /* System library. */ #include <sys_defs.h> +#include <sys/socket.h> #include <unistd.h> #include <string.h> @@ -53,13 +58,42 @@ #include <msg.h> #include <connect.h> #include <iostuff.h> +#include <mymalloc.h> +#include <events.h> #include <trigger.h> +struct inet_trigger { + int fd; + char *service; +}; + +/* inet_trigger_event - disconnect from peer */ + +static void inet_trigger_event(int event, char *context) +{ + struct inet_trigger *ip = (struct inet_trigger *) context; + static char *myname = "inet_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, ip->service); + event_disable_readwrite(ip->fd); + event_cancel_timer(inet_trigger_event, context); + if (close(ip->fd) < 0) + msg_warn("%s: close %s: %m", myname, ip->service); + myfree(ip->service); + myfree((char *) ip); +} + + /* inet_trigger - wakeup INET-domain server */ int inet_trigger(const char *service, const char *buf, int len, int timeout) { char *myname = "inet_trigger"; + struct inet_trigger *ip; int fd; if (msg_verbose > 1) @@ -73,19 +107,24 @@ int inet_trigger(const char *service, const char *buf, int len, int timeout) msg_warn("%s: connect to %s: %m", myname, service); return (-1); } + close_on_exec(fd, CLOSE_ON_EXEC); + ip = (struct inet_trigger *) mymalloc(sizeof(*ip)); + ip->fd = fd; + ip->service = mystrdup(service); /* * Write the request... */ - if (write_buf(fd, buf, len, timeout) < 0) + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) if (msg_verbose) msg_warn("%s: write to %s: %m", myname, service); /* - * Disconnect. + * Wakeup when the peer disconnects, or when we lose patience. */ - if (close(fd) < 0) - if (msg_verbose) - msg_warn("%s: close %s: %m", myname, service); + if (timeout > 0) + event_request_timer(inet_trigger_event, (char *) ip, timeout + 100); + event_enable_read(fd, inet_trigger_event, (char *) ip); return (0); } diff --git a/gnu/dist/postfix/src/util/intv.c b/gnu/dist/postfix/src/util/intv.c new file mode 100644 index 00000000000..1d117fe0209 --- /dev/null +++ b/gnu/dist/postfix/src/util/intv.c @@ -0,0 +1,114 @@ +/*++ +/* NAME +/* intv 3 +/* SUMMARY +/* integer array utilities +/* SYNOPSIS +/* #include <intv.h> +/* +/* INTV *intv_alloc(len) +/* int len; +/* +/* INTV *intv_free(intvp) +/* INTV *intvp; +/* +/* void intv_add(intvp, count, arg, ...) +/* INTV *intvp; +/* int count; +/* int *arg; +/* DESCRIPTION +/* The functions in this module manipulate arrays of integers. +/* An INTV structure contains the following members: +/* .IP len +/* The actual length of the \fIintv\fR array. +/* .IP intc +/* The number of \fIintv\fR elements used. +/* .IP intv +/* An array of integer values. +/* .PP +/* intv_alloc() returns an empty integer array of the requested +/* length. The result is ready for use by intv_add(). +/* +/* intv_add() copies zero or more integers and adds them to the +/* specified integer array. +/* +/* intv_free() releases storage for an integer array, and conveniently +/* returns a null pointer. +/* SEE ALSO +/* msg(3) diagnostics interface +/* DIAGNOSTICS +/* Fatal errors: memory allocation problem. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include <sys_defs.h> +#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ +#include <stdarg.h> + +/* Application-specific. */ + +#include "mymalloc.h" +#include "msg.h" +#include "intv.h" + +/* intv_free - destroy integer array */ + +INTV *intv_free(INTV *intvp) +{ + myfree((char *) intvp->intv); + myfree((char *) intvp); + return (0); +} + +/* intv_alloc - initialize integer array */ + +INTV *intv_alloc(int len) +{ + INTV *intvp; + + /* + * Sanity check. + */ + if (len < 1) + msg_panic("intv_alloc: bad array length %d", len); + + /* + * Initialize. + */ + intvp = (INTV *) mymalloc(sizeof(*intvp)); + intvp->len = len; + intvp->intv = (int *) mymalloc(intvp->len * sizeof(intvp->intv[0])); + intvp->intc = 0; + return (intvp); +} + +/* intv_add - add integer to vector */ + +void intv_add(INTV *intvp, int count,...) +{ + va_list ap; + + /* + * Make sure that always intvp->intc < intvp->len. + */ + va_start(ap, count); + while (count-- > 0) { + if (intvp->intc >= intvp->len) { + intvp->len *= 2; + intvp->intv = (int *) myrealloc((char *) intvp->intv, + intvp->len * sizeof(int)); + } + intvp->intv[intvp->intc++] = va_arg(ap, int); + } + va_end(ap); +} diff --git a/gnu/dist/postfix/src/util/intv.h b/gnu/dist/postfix/src/util/intv.h new file mode 100644 index 00000000000..d84c5b4b191 --- /dev/null +++ b/gnu/dist/postfix/src/util/intv.h @@ -0,0 +1,38 @@ +#ifndef _INTV_H_INCLUDED_ +#define _INTV_H_INCLUDED_ + +/*++ +/* NAME +/* intv 3h +/* SUMMARY +/* string array utilities +/* SYNOPSIS +/* #include "intv.h" + DESCRIPTION + .nf + + /* + * External interface. + */ +typedef struct INTV { + int len; /* number of array elements */ + int intc; /* array elements in use */ + int *intv; /* integer array */ +} INTV; + +extern INTV *intv_alloc(int); +extern void intv_add(INTV *, int,...); +extern INTV *intv_free(INTV *); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/util/make_dirs.c b/gnu/dist/postfix/src/util/make_dirs.c index b3efcddbf60..ad1b451de70 100644 --- a/gnu/dist/postfix/src/util/make_dirs.c +++ b/gnu/dist/postfix/src/util/make_dirs.c @@ -54,6 +54,7 @@ int make_dirs(const char *path, int perms) int saved_ch; struct stat st; int ret; + mode_t saved_mode = 0; /* * Initialize. Make a copy of the path that we can safely clobber. @@ -78,14 +79,31 @@ int make_dirs(const char *path, int perms) ret = -1; break; } + saved_mode = st.st_mode; } else { if (errno != ENOENT) break; + + /* + * Create a new directory. Unfortunately, mkdir(2) has no + * equivalent of open(2)'s O_CREAT|O_EXCL safety net, so we must + * require that the parent directory is not world writable. + * Detecting a lost race condition after the fact is not + * sufficient, as an attacker could repeat the attack and add one + * directory level at a time. + */ + if (saved_mode & S_IWOTH) { + msg_warn("refusing to mkdir %s: parent directory is writable by everyone", + saved_path); + errno = EPERM; + ret = -1; + break; + } if ((ret = mkdir(saved_path, perms)) < 0) { if (errno != EEXIST) break; /* Race condition? */ - if ((ret = stat(saved_path, &st)) < 0) + if ((ret = stat(saved_path, &st)) < 0) break; if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; diff --git a/gnu/dist/postfix/src/util/match_list.h b/gnu/dist/postfix/src/util/match_list.h index aba88b8f694..910ae713bf2 100644 --- a/gnu/dist/postfix/src/util/match_list.h +++ b/gnu/dist/postfix/src/util/match_list.h @@ -15,9 +15,9 @@ * External interface. */ typedef struct MATCH_LIST MATCH_LIST; -typedef int (*MATCH_LIST_FN) (const char *, const char *); +typedef int (*MATCH_LIST_FN) (int, const char *, const char *); -extern MATCH_LIST *match_list_init(const char *, int,...); +extern MATCH_LIST *match_list_init(int, const char *, int,...); extern int match_list_match(MATCH_LIST *,...); extern void match_list_free(MATCH_LIST *); diff --git a/gnu/dist/postfix/src/util/match_ops.h b/gnu/dist/postfix/src/util/match_ops.h index a938c04fb34..e147f7b50dd 100644 --- a/gnu/dist/postfix/src/util/match_ops.h +++ b/gnu/dist/postfix/src/util/match_ops.h @@ -13,9 +13,13 @@ /* External interface. */ -extern int match_string(const char *, const char *); -extern int match_hostname(const char *, const char *); -extern int match_hostaddr(const char *, const char *); +#define MATCH_FLAG_NONE 0 +#define MATCH_FLAG_PARENT (1<<0) +#define MATCH_FLAG_ALL (MATCH_FLAG_PARENT) + +extern int match_string(int, const char *, const char *); +extern int match_hostname(int, const char *, const char *); +extern int match_hostaddr(int, const char *, const char *); /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/util/msg.c b/gnu/dist/postfix/src/util/msg.c index 08379a90b86..683359b2b37 100644 --- a/gnu/dist/postfix/src/util/msg.c +++ b/gnu/dist/postfix/src/util/msg.c @@ -20,6 +20,10 @@ /* NORETURN msg_fatal(format, ...) /* const char *format; /* +/* NORETURN msg_fatal_status(status, format, ...) +/* int status; +/* const char *format; +/* /* NORETURN msg_panic(format, ...) /* const char *format; /* @@ -48,11 +52,14 @@ /* msg_fatal() reports an unrecoverable error and terminates the program /* with a non-zero exit status. /* +/* msg_fatal_status() reports an unrecoverable error and terminates the +/* program with the specified exit status. +/* /* msg_panic() reports an internal inconsistency, terminates the /* program immediately (i.e. without calling the optional user-specified /* cleanup routine), and forces a core dump when possible. /* -/* msg_cleanup() specifies a function that msg_fatal() should +/* msg_cleanup() specifies a function that msg_fatal[_status]() should /* invoke before terminating the program, and returns the /* current function pointer. Specify a null argument to disable /* this feature. @@ -163,6 +170,23 @@ NORETURN msg_fatal(const char *fmt,...) exit(1); } +/* msg_fatal_status - report error and terminate gracefully */ + +NORETURN msg_fatal_status(int status, const char *fmt,...) +{ + va_list ap; + + if (msg_exiting++ == 0) { + va_start(ap, fmt); + msg_vprintf(MSG_FATAL, fmt, ap); + va_end(ap); + if (msg_cleanup_fn) + msg_cleanup_fn(); + } + sleep(1); + exit(status); +} + /* msg_panic - report error and dump core */ NORETURN msg_panic(const char *fmt,...) diff --git a/gnu/dist/postfix/src/util/msg.h b/gnu/dist/postfix/src/util/msg.h index fae8a2327c9..42198a2a71b 100644 --- a/gnu/dist/postfix/src/util/msg.h +++ b/gnu/dist/postfix/src/util/msg.h @@ -22,6 +22,7 @@ extern void PRINTFLIKE(1, 2) msg_info(const char *,...); extern void PRINTFLIKE(1, 2) msg_warn(const char *,...); extern void PRINTFLIKE(1, 2) msg_error(const char *,...); extern NORETURN PRINTFLIKE(1, 2) msg_fatal(const char *,...); +extern NORETURN PRINTFLIKE(2, 3) msg_fatal_status(int, const char *,...); extern NORETURN PRINTFLIKE(1, 2) msg_panic(const char *,...); extern int msg_error_limit(int); diff --git a/gnu/dist/postfix/src/util/mymalloc.c b/gnu/dist/postfix/src/util/mymalloc.c index e20531c0605..61014a800e0 100644 --- a/gnu/dist/postfix/src/util/mymalloc.c +++ b/gnu/dist/postfix/src/util/mymalloc.c @@ -90,7 +90,10 @@ typedef struct MBLOCK { int signature; /* set when block is active */ int length; /* user requested length */ - char payload[1]; /* actually a bunch of bytes */ + union { + ALIGN_TYPE align; + char payload[1]; /* actually a bunch of bytes */ + } u; } MBLOCK; #define SIGNATURE 0xdead @@ -99,7 +102,7 @@ typedef struct MBLOCK { #define CHECK_IN_PTR(ptr, real_ptr, len, fname) { \ if (ptr == 0) \ msg_panic("%s: null pointer input", fname); \ - real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, payload[0])); \ + real_ptr = (MBLOCK *) (ptr - offsetof(MBLOCK, u.payload[0])); \ if (real_ptr->signature != SIGNATURE) \ msg_panic("%s: corrupt or unallocated memory block", fname); \ real_ptr->signature = 0; \ @@ -110,10 +113,10 @@ typedef struct MBLOCK { #define CHECK_OUT_PTR(ptr, real_ptr, len) { \ real_ptr->signature = SIGNATURE; \ real_ptr->length = len; \ - ptr = real_ptr->payload; \ + ptr = real_ptr->u.payload; \ } -#define SPACE_FOR(len) (offsetof(MBLOCK, payload[0]) + len) +#define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len) /* mymalloc - allocate memory or bust */ @@ -165,6 +168,8 @@ void myfree(char *ptr) char *mystrdup(const char *str) { + if (str == 0) + msg_panic("mystrdup: null pointer argument"); return (strcpy(mymalloc(strlen(str) + 1), str)); } @@ -173,10 +178,12 @@ char *mystrdup(const char *str) char *mystrndup(const char *str, int len) { char *result; - int slen; + char *cp; - if ((slen = strlen(str)) < len) - len = slen; + if (str == 0) + msg_panic("mystrndup: null pointer argument"); + if ((cp = memchr(str, 0, len)) != 0) + len = cp - str; result = memcpy(mymalloc(len + 1), str, len); result[len] = 0; return (result); @@ -186,5 +193,7 @@ char *mystrndup(const char *str, int len) char *mymemdup(const char *ptr, int len) { + if (ptr == 0) + msg_panic("mymemdup: null pointer argument"); return (memcpy(mymalloc(len), ptr, len)); } diff --git a/gnu/dist/postfix/src/util/myrand.c b/gnu/dist/postfix/src/util/myrand.c new file mode 100644 index 00000000000..39fcbfd9e47 --- /dev/null +++ b/gnu/dist/postfix/src/util/myrand.c @@ -0,0 +1,63 @@ +/*++ +/* NAME +/* myrand 3 +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include <myrand.h> +/* +/* void mysrand(seed) +/* int seed; +/* +/* int myrand() +/* DESCRIPTION +/* This module implements a wrapper for the portable, pseudo-random +/* number generator. The wrapper adds automatic initialization. +/* +/* mysrand() performs initialization. This call may be skipped. +/* +/* myrand() returns a pseudo-random number in the range [0, RAND_MAX]. +/* If mysrand() was not called, it is invoked with the process ID +/* ex-or-ed with the time of day in seconds. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* WARNING +/* Do not use this code for generating unpredictable numbers. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> + +/* Utility library. */ + +#include <myrand.h> + +static int myrand_initdone = 0; + +/* mysrand - initialize */ + +void mysrand(int seed) +{ + srand(seed); + myrand_initdone = 1; +} + +/* myrand - pseudo-random number */ + +int myrand(void) +{ + if (myrand_initdone == 0) + mysrand(getpid() ^ time((time_t *) 0)); + return (rand()); +} diff --git a/gnu/dist/postfix/src/util/myrand.h b/gnu/dist/postfix/src/util/myrand.h new file mode 100644 index 00000000000..49c5abb8433 --- /dev/null +++ b/gnu/dist/postfix/src/util/myrand.h @@ -0,0 +1,31 @@ +#ifndef _MYRAND_H_INCLUDED_ +#define _MYRAND_H_INCLUDED_ + +/*++ +/* NAME +/* myrand 3h +/* SUMMARY +/* rand wrapper +/* SYNOPSIS +/* #include <myrand.h> +/* DESCRIPTION +/* .nf + + /* + * External interface. + */ +extern void mysrand(int); +extern int myrand(void); + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/util/netstring.c b/gnu/dist/postfix/src/util/netstring.c new file mode 100644 index 00000000000..3f3e73112ad --- /dev/null +++ b/gnu/dist/postfix/src/util/netstring.c @@ -0,0 +1,351 @@ +/*++ +/* NAME +/* netstring 3 +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include <netstring.h> +/* +/* void netstring_setup(stream, timeout) +/* VSTREAM *stream; +/* int timeout; +/* +/* void netstring_except(stream, exception) +/* VSTREAM *stream; +/* int exception; +/* +/* VSTRING *netstring_get(stream, buf, limit) +/* VSTREAM *stream; +/* VSTRING *buf; +/* int limit; +/* +/* void netstring_put(stream, data, len) +/* VSTREAM *stream; +/* const char *data; +/* int len; +/* +/* void netstring_put_multi(stream, data, len, data, len, ..., 0) +/* VSTREAM *stream; +/* const char *data; +/* int len; +/* +/* void NETSTRING_PUT_BUF(stream, buf) +/* VSTREAM *stream; +/* VSTRING *buf; +/* +/* void netstring_fflush(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_memcpy(buf, data, len) +/* VSTRING *buf; +/* const char *data; +/* int len; +/* +/* VSTRING *netstring_memcat(buf, data, len) +/* VSTRING *buf; +/* const char *src; +/* int len; +/* AUXILIARY ROUTINES +/* int netstring_get_length(stream) +/* VSTREAM *stream; +/* +/* VSTRING *netstring_get_data(stream, buf, len) +/* VSTREAM *stream; +/* VSTRING *buf; +/* int len; +/* +/* void netstring_get_terminator(stream) +/* VSTREAM *stream; +/* DESCRIPTION +/* This module reads and writes netstrings with error detection: +/* timeouts, unexpected end-of-file, or format errors. Netstring +/* is a data format designed by Daniel Bernstein. +/* +/* netstring_setup() arranges for a time limit on the netstring +/* read and write operations described below. +/* This routine alters the behavior of streams as follows: +/* .IP \(bu +/* The read/write timeout is set to the specified value. +/* .IP \(bu +/* The stream is configured to enable exception handling. +/* .PP +/* netstring_except() raises the specified exception on the +/* named stream. See the DIAGNOSTICS section below. +/* +/* netstring_get() reads a netstring from the specified stream +/* and extracts its content. The limit specifies a maximal size. +/* Specify zero to disable the size limit. The result is not null +/* terminated. The result value is the buf argument. +/* +/* netstring_put() encapsulates the specified string as a netstring +/* and sends the result to the specified stream. +/* The stream output buffer is not flushed. +/* +/* netstring_put_multi() encapsulates the content of multiple strings +/* as one netstring and sends the result to the specified stream. The +/* argument list must be terminated with a null data pointer. +/* The stream output buffer is not flushed. +/* +/* NETSTRING_PUT_BUF() is a macro that provides a VSTRING-based +/* wrapper for the netstring_put() routine. +/* +/* netstring_fflush() flushes the output buffer of the specified +/* stream and handles any errors. +/* +/* netstring_memcpy() encapsulates the specified data as a netstring +/* and copies the result over the specified buffer. The result +/* value is the buffer. +/* +/* netstring_memcat() encapsulates the specified data as a netstring +/* and appends the result to the specified buffer. The result +/* value is the buffer. +/* +/* The following routines provide low-level access to a netstring +/* stream. +/* +/* netstring_get_length() reads a length field from the specified +/* stream, and absorbs the netstring length field terminator. +/* +/* netstring_get_data() reads the specified number of bytes from the +/* specified stream into the specified buffer, and absorbs the +/* netstring terminator. The result value is the buf argument. +/* +/* netstring_get_terminator() reads the netstring terminator from +/* the specified stream. +/* DIAGNOSTICS +/* .fi +/* .ad +/* In case of error, a vstream_longjmp() call is performed to the +/* context specified with vstream_setjmp(). +/* Error codes passed along with vstream_longjmp() are: +/* .IP NETSTRING_ERR_EOF +/* An I/O error happened, or the peer has disconnected unexpectedly. +/* .IP NETSTRING_ERR_TIME +/* The time limit specified to netstring_setup() was exceeded. +/* .IP NETSTRING_ERR_FORMAT +/* The input contains an unexpected character value. +/* .IP NETSTRING_ERR_SIZE +/* The input is larger than acceptable. +/* BUGS +/* The timeout deadline affects all I/O on the named stream, not +/* just the I/O done on behalf of this module. +/* +/* The timeout deadline overwrites any previously set up state on +/* the named stream. +/* +/* netstrings are not null terminated, which makes printing them +/* a bit awkward. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* SEE ALSO +/* http://cr.yp.to/proto/netstrings.txt, netstring definition +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdarg.h> +#include <ctype.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> +#include <vstring.h> +#include <netstring.h> + +/* Application-specific. */ + +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* netstring_setup - initialize netstring stream */ + +void netstring_setup(VSTREAM *stream, int timeout) +{ + vstream_control(stream, + VSTREAM_CTL_TIMEOUT, timeout, + VSTREAM_CTL_EXCEPT, + VSTREAM_CTL_END); +} + +/* netstring_except - process netstring stream exception */ + +void netstring_except(VSTREAM *stream, int exception) +{ + vstream_longjmp(stream, exception); +} + +/* netstring_get_length - read netstring length + terminator */ + +int netstring_get_length(VSTREAM *stream) +{ + char *myname = "netstring_get_length"; + int len = 0; + int ch; + + for (;;) { + switch (ch = VSTREAM_GETC(stream)) { + case VSTREAM_EOF: + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + case ':': + if (msg_verbose > 1) + msg_info("%s: read netstring length %d", myname, len); + return (len); + default: + if (!ISDIGIT(ch)) + netstring_except(stream, NETSTRING_ERR_FORMAT); + len = len * 10 + ch - '0'; + break; + } + } +} + +/* netstring_get_data - read netstring payload + terminator */ + +VSTRING *netstring_get_data(VSTREAM *stream, VSTRING *buf, int len) +{ + char *myname = "netstring_get_data"; + + /* + * Allocate buffer space. + */ + VSTRING_RESET(buf); + VSTRING_SPACE(buf, len); + + /* + * Read the payload and absorb the terminator. + */ + if (vstream_fread(stream, STR(buf), len) != len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + if (msg_verbose > 1) + msg_info("%s: read netstring data %.*s", + myname, len < 30 ? len : 30, STR(buf)); + netstring_get_terminator(stream); + + /* + * Position the buffer. + */ + VSTRING_AT_OFFSET(buf, len); + return (buf); +} + +/* netstring_get_terminator - absorb netstring terminator */ + +void netstring_get_terminator(VSTREAM *stream) +{ + if (VSTREAM_GETC(stream) != ',') + netstring_except(stream, NETSTRING_ERR_FORMAT); +} + +/* netstring_get - read string from netstring stream */ + +VSTRING *netstring_get(VSTREAM *stream, VSTRING *buf, int limit) +{ + int len; + + len = netstring_get_length(stream); + if (limit && len > limit) + netstring_except(stream, NETSTRING_ERR_SIZE); + netstring_get_data(stream, buf, len); + return (buf); +} + +/* netstring_put - send string as netstring */ + +void netstring_put(VSTREAM *stream, const char *data, int len) +{ + char *myname = "netstring_put"; + + if (msg_verbose > 1) + msg_info("%s: write netstring len %d data %.*s", + myname, len, len < 30 ? len : 30, data); + vstream_fprintf(stream, "%d:", len); + vstream_fwrite(stream, data, len); + VSTREAM_PUTC(',', stream); +} + +/* netstring_put_multi - send multiple strings as one netstring */ + +void netstring_put_multi(VSTREAM *stream,...) +{ + char *myname = "netstring_put_multi"; + int total; + char *data; + int data_len; + va_list ap; + + /* + * Figure out the total result size. + */ + va_start(ap, stream); + for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len) + if ((data_len = va_arg(ap, int)) < 0) + msg_panic("netstring_put_multi: bad data length %d", data_len); + va_end(ap); + + /* + * Debugging support. + */ + if (msg_verbose > 1) { + va_start(ap, stream); + data = va_arg(ap, char *); + data_len = va_arg(ap, int); + msg_info("%s: write netstring len %d data %.*s", + myname, total, data_len < 30 ? data_len : 30, data); + va_end(ap); + } + + /* + * Send the length, content and terminator. + */ + vstream_fprintf(stream, "%d:", total); + va_start(ap, stream); + while ((data = va_arg(ap, char *)) != 0) { + data_len = va_arg(ap, int); + if (data_len > 0) + if (vstream_fwrite(stream, data, data_len) != data_len) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); + va_end(ap); + } + vstream_fwrite(stream, ",", 1); +} + +/* netstring_fflush - flush netstring stream */ + +void netstring_fflush(VSTREAM *stream) +{ + if (vstream_fflush(stream) == VSTREAM_EOF) + netstring_except(stream, vstream_ftimeout(stream) ? + NETSTRING_ERR_TIME : NETSTRING_ERR_EOF); +} + +/* netstring_memcpy - copy data as in-memory netstring */ + +VSTRING *netstring_memcpy(VSTRING *buf, const char *src, int len) +{ + vstring_sprintf(buf, "%d:", len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} + +/* netstring_memcat - append data as in-memory netstring */ + +VSTRING *netstring_memcat(VSTRING *buf, const char *src, int len) +{ + vstring_sprintf_append(buf, "%d:", len); + vstring_memcat(buf, src, len); + VSTRING_ADDCH(buf, ','); + return (buf); +} diff --git a/gnu/dist/postfix/src/util/netstring.h b/gnu/dist/postfix/src/util/netstring.h new file mode 100644 index 00000000000..6b82d90e4e8 --- /dev/null +++ b/gnu/dist/postfix/src/util/netstring.h @@ -0,0 +1,54 @@ +#ifndef _NETSTRING_H_INCLUDED_ +#define _NETSTRING_H_INCLUDED_ + +/*++ +/* NAME +/* netstring 3h +/* SUMMARY +/* netstring stream I/O support +/* SYNOPSIS +/* #include <netstring.h> +/* DESCRIPTION +/* .nf + + /* + * Utility library. + */ +#include <vstring.h> +#include <vstream.h> + + /* + * External interface. + */ +#define NETSTRING_ERR_EOF 1 /* unexpected disconnect */ +#define NETSTRING_ERR_TIME 2 /* time out */ +#define NETSTRING_ERR_FORMAT 3 /* format error */ +#define NETSTRING_ERR_SIZE 4 /* netstring too large */ + +extern void netstring_except(VSTREAM *, int); +extern void netstring_setup(VSTREAM *, int); +extern int netstring_get_length(VSTREAM *); +extern VSTRING *netstring_get_data(VSTREAM *, VSTRING *, int); +extern void netstring_get_terminator(VSTREAM *); +extern VSTRING *netstring_get(VSTREAM *, VSTRING *, int); +extern void netstring_put(VSTREAM *, const char *, int); +extern void netstring_put_multi(VSTREAM *,...); +extern void netstring_fflush(VSTREAM *); +extern VSTRING *netstring_memcpy(VSTRING *, const char *, int); +extern VSTRING *netstring_memcat(VSTRING *, const char *, int); + +#define NETSTRING_PUT_BUF(str, buf) \ + netstring_put((str), vstring_str(buf), VSTRING_LEN(buf)) + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +#endif diff --git a/gnu/dist/postfix/src/util/rand_sleep.c b/gnu/dist/postfix/src/util/rand_sleep.c index d119f074f57..33ffecb51c4 100644 --- a/gnu/dist/postfix/src/util/rand_sleep.c +++ b/gnu/dist/postfix/src/util/rand_sleep.c @@ -45,6 +45,7 @@ /* Utility library. */ #include <msg.h> +#include <myrand.h> #include <iostuff.h> /* rand_sleep - block for random time */ @@ -52,7 +53,6 @@ void rand_sleep(unsigned delay, unsigned variation) { char *myname = "rand_sleep"; - static pid_t my_pid; unsigned usec; /* @@ -66,9 +66,7 @@ void rand_sleep(unsigned delay, unsigned variation) /* * Use the semi-crappy random number generator. */ - if (my_pid == 0) - srand((my_pid = getpid()) ^ time((time_t *) 0)); - usec = (delay - variation / 2) + variation * (double) rand() / RAND_MAX; + usec = (delay - variation / 2) + variation * (double) myrand() / RAND_MAX; doze(usec); } diff --git a/gnu/dist/postfix/src/util/readlline.c b/gnu/dist/postfix/src/util/readlline.c index 314d6962bf0..9be31eab109 100644 --- a/gnu/dist/postfix/src/util/readlline.c +++ b/gnu/dist/postfix/src/util/readlline.c @@ -6,30 +6,37 @@ /* SYNOPSIS /* #include <readlline.h> /* -/* VSTRING *readlline(buf, fp, lineno, stripnl) +/* VSTRING *readlline(buf, fp, lineno) /* VSTRING *buf; /* VSTREAM *fp; /* int *lineno; -/* int stripnl; /* DESCRIPTION /* readlline() reads one logical line from the named stream. -/* A line that starts with whitespace is a continuation of -/* the previous line. When the stripnl argument is non-zero, -/* the newline between continued lines -/* is deleted from the input. The result value is the input -/* buffer argument or a null pointer when no input is found. +/* .IP "blank lines and comments" +/* Empty lines and whitespace-only lines are ignored, as +/* are lines whose first non-whitespace character is a `#'. +/* .IP "multi-line text" +/* A logical line starts with non-whitespace text. A line that +/* starts with whitespace continues a logical line. +/* .PP +/* The result value is the input buffer argument or a null pointer +/* when no input is found. /* /* Arguments: /* .IP buf -/* A variable-length buffer for input. +/* A variable-length buffer for input. The result is null terminated. /* .IP fp /* Handle to an open stream. /* .IP lineno /* A null pointer, or a pointer to an integer that is incremented -/* after reading a newline. -/* .IP stripnl -/* Non-zero to strip newlines. readlline.h provides the symbolic -/* constants READLL_STRIPNL and READLL_KEEPNL for convenience. +/* after reading a newline character. +/* .RE +/* DIAGNOSTICS +/* Warning: a continuation line that does not continue preceding text. +/* The invalid input is ignored, to avoid complicating caller code. +/* SECURITY +/* readlline() imposes no logical line length limit therefore it +/* should be used for reading trusted information only. /* LICENSE /* .ad /* .fi @@ -44,40 +51,73 @@ /* System library. */ #include <sys_defs.h> +#include <ctype.h> /* Utility library. */ +#include "msg.h" #include "vstream.h" #include "vstring.h" #include "readlline.h" +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) +#define END(x) vstring_end(x) + /* readlline - read one logical line */ -VSTRING *readlline(VSTRING *buf, VSTREAM *fp, int *lineno, int stripnl) +VSTRING *readlline(VSTRING *buf, VSTREAM *fp, int *lineno) { int ch; int next; + int start; + char *cp; + + VSTRING_RESET(buf); /* - * Lines that start with whitespace continue the preceding line. + * Ignore comment lines, all whitespace lines, and empty lines. Terminate + * at EOF or at the beginning of the next logical line. */ - VSTRING_RESET(buf); - while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF) { - if (ch == '\n') { - if (stripnl == 0) - VSTRING_ADDCH(buf, ch); - if (lineno) - *lineno += 1; - if ((next = VSTREAM_GETC(fp)) == ' ' || next == '\t') { - ch = next; - } else { - if (next != VSTREAM_EOF) - vstream_ungetc(fp, next); + for (;;) { + /* Read one line, possibly not newline terminated. */ + start = LEN(buf); + while ((ch = VSTREAM_GETC(fp)) != VSTREAM_EOF && ch != '\n') + VSTRING_ADDCH(buf, ch); + if (ch == '\n' && lineno != 0) + *lineno += 1; + /* Ignore comment line, all whitespace line, or empty line. */ + for (cp = STR(buf) + start; cp < END(buf) && ISSPACE(*cp); cp++) + /* void */ ; + if (cp == END(buf) || *cp == '#') + vstring_truncate(buf, start); + /* Terminate at EOF or at the beginning of the next logical line. */ + if (ch == VSTREAM_EOF) + break; + if (LEN(buf) > 0) { + if ((next = VSTREAM_GETC(fp)) != VSTREAM_EOF) + vstream_ungetc(fp, next); + if (next != '#' && !ISSPACE(next)) break; - } } - VSTRING_ADDCH(buf, ch); } + + /* + * Invalid input: continuing text without preceding text. Allowing this + * would complicate "postconf -e", which implements its own multi-line + * parsing routine. Do not abort, just warn, so that critical programs + * like postmap do not leave behind a truncated table. + */ + if (LEN(buf) > 0 && ISSPACE(*STR(buf))) { + msg_warn("%s: logical line must not start with whitespace: \"%.30s%s\"", + VSTREAM_PATH(fp), STR(buf), + LEN(buf) > 30 ? "..." : ""); + return (readlline(buf, fp, lineno)); + } + + /* + * Done. + */ VSTRING_TERMINATE(buf); - return (VSTRING_LEN(buf) || ch == '\n' ? buf : 0); + return (LEN(buf) > 0 ? buf : 0); } diff --git a/gnu/dist/postfix/src/util/readlline.h b/gnu/dist/postfix/src/util/readlline.h index 00658433cc2..8a8bd548711 100644 --- a/gnu/dist/postfix/src/util/readlline.h +++ b/gnu/dist/postfix/src/util/readlline.h @@ -20,10 +20,7 @@ /* * External interface. */ -extern VSTRING *readlline(VSTRING *, VSTREAM *, int *, int); - -#define READLL_STRIPNL 1 -#define READLL_KEEPNL 0 +extern VSTRING *readlline(VSTRING *, VSTREAM *, int *); /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/util/safe_open.c b/gnu/dist/postfix/src/util/safe_open.c index e1e88f8b475..d20c9404c1a 100644 --- a/gnu/dist/postfix/src/util/safe_open.c +++ b/gnu/dist/postfix/src/util/safe_open.c @@ -41,8 +41,9 @@ /* Panic: interface violations. /* /* A null result means there was a problem. The nature of the -/* problem is returned via the \fIwhy\fR buffer; some errors -/* cannot be reported via \fIerrno\fR. +/* problem is returned via the \fIwhy\fR buffer; when an error +/* cannot be reported via \fIerrno\fR, the generic value EPERM +/* (operation not permitted) is used instead. /* HISTORY /* .fi /* .ad @@ -54,7 +55,7 @@ /* be fooled by delaying the open() until the inode found with /* lstat() has been re-used for a sensitive file (article /* <20000103212443.A5807@monad.swb.de> posted to bugtraq on -/* Jan 3, 2000). This can be a concern for a set-uid process +/* Jan 3, 2000). This can be a concern for a set-ugid process /* that runs under the control of a user and that can be /* manipulated with start/stop signals. /* LICENSE @@ -113,8 +114,10 @@ static VSTREAM *safe_open_exist(const char *path, int flags, } else if (fstat_st->st_nlink != 1) { vstring_sprintf(why, "file has %d hard links", (int) fstat_st->st_nlink); + errno = EPERM; } else if (S_ISDIR(fstat_st->st_mode)) { vstring_sprintf(why, "file is a directory"); + errno = EISDIR; } /* @@ -135,10 +138,12 @@ static VSTREAM *safe_open_exist(const char *path, int flags, */ else if (lstat(path, &lstat_st) < 0) { vstring_sprintf(why, "file status changed unexpectedly: %m"); + errno = EPERM; } else if (S_ISLNK(lstat_st.st_mode)) { if (lstat_st.st_uid == 0) return (fp); vstring_sprintf(why, "file is a symbolic link"); + errno = EPERM; } else if (fstat_st->st_dev != lstat_st.st_dev || fstat_st->st_ino != lstat_st.st_ino #ifdef HAS_ST_GEN @@ -147,6 +152,7 @@ static VSTREAM *safe_open_exist(const char *path, int flags, || fstat_st->st_nlink != lstat_st.st_nlink || fstat_st->st_mode != lstat_st.st_mode) { vstring_sprintf(why, "file status changed unexpectedly"); + errno = EPERM; } /* @@ -158,9 +164,7 @@ static VSTREAM *safe_open_exist(const char *path, int flags, /* * End up here in case of fstat()/lstat() problems or inconsistencies. - * Reset errno to reduce confusion. */ - errno = 0; vstream_fclose(fp); return (0); } @@ -190,7 +194,7 @@ static VSTREAM *safe_open_create(const char *path, int flags, int mode, if (CHANGE_OWNER(user, group) && fchown(vstream_fileno(fp), user, group) < 0) { - vstring_sprintf(why, "cannot change file ownership: %m"); + msg_warn("%s: cannot change file ownership: %m", path); } /* diff --git a/gnu/dist/postfix/src/util/sane_accept.c b/gnu/dist/postfix/src/util/sane_accept.c index d9d8464d0ad..9ae9f6e3f49 100644 --- a/gnu/dist/postfix/src/util/sane_accept.c +++ b/gnu/dist/postfix/src/util/sane_accept.c @@ -36,6 +36,7 @@ /* Utility library. */ +#include "msg.h" #include "sane_accept.h" /* sane_accept - sanitize accept() error returns */ @@ -55,6 +56,10 @@ int sane_accept(int sock, struct sockaddr * sa, SOCKADDR_SIZE *len) EWOULDBLOCK, 0, }; + static int accept_warn_errors[] = { + ECONNABORTED, + 0, + }; int count; int err; int fd; @@ -68,6 +73,13 @@ int sane_accept(int sock, struct sockaddr * sa, SOCKADDR_SIZE *len) * XXX LINUX < 2.1 accept() wakes up before the three-way handshake is * complete, so it can fail with ECONNRESET and other "false alarm" * indications. + * + * XXX FreeBSD 4.2-STABLE accept() returns ECONNABORTED when a UNIX-domain + * client has disconnected in the mean time. The data that was sent with + * connect() write() close() is lost, even though the write() and close() + * reported successful completion. This was fixed shortly before FreeBSD + * 4.3. However, other systems may make that same mistake again, so we're + * adding a special warning. */ if ((fd = accept(sock, sa, len)) < 0) { for (count = 0; (err = accept_ok_errors[count]) != 0; count++) { @@ -76,6 +88,13 @@ int sane_accept(int sock, struct sockaddr * sa, SOCKADDR_SIZE *len) break; } } + for (count = 0; (err = accept_warn_errors[count]) != 0; count++) { + if (errno == err) { + msg_warn("accept: %m"); + errno = EAGAIN; + break; + } + } } return (fd); } diff --git a/gnu/dist/postfix/src/util/split_nameval.c b/gnu/dist/postfix/src/util/split_nameval.c new file mode 100644 index 00000000000..f3e519bad1e --- /dev/null +++ b/gnu/dist/postfix/src/util/split_nameval.c @@ -0,0 +1,94 @@ +/*++ +/* NAME +/* split_nameval 3 +/* SUMMARY +/* name-value splitter +/* SYNOPSIS +/* #include <split_nameval.h> +/* +/* const char *split_nameval(buf, name, value) +/* char *buf; +/* char **name; +/* char **value; +/* DESCRIPTION +/* split_nameval() takes a logical line from readlline() and expects +/* text of the form "name = value" or "name =". The buffer +/* argument is broken up into name and value substrings. +/* +/* Arguments: +/* .IP buf +/* Result from readlline() or equivalent. The buffer is modified. +/* .IP name +/* Upon successful completion, this is set to the name +/* substring. +/* .IP value +/* Upon successful completion, this is set to the value +/* substring. +/* FEATURES +/* SEE ALSO +/* dict(3) mid-level dictionary routines +/* BUGS +/* DIAGNOSTICS +/* Fatal errors: out of memory. +/* +/* The result is a null pointer in case of success, a string +/* describing the error otherwise: missing '=' after attribute +/* name; missing attribute name. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System libraries. */ + +#include "sys_defs.h" +#include <ctype.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <stringops.h> + +/* split_nameval - split text into name and value */ + +const char *split_nameval(char *buf, char **name, char **value) +{ + char *np; /* name substring */ + char *vp; /* value substring */ + char *cp; + char *ep; + + /* + * Ugly macros to make complex expressions less unreadable. + */ +#define SKIP(start, var, cond) \ + for (var = start; *var && (cond); var++); + +#define TRIM(s) { \ + char *p; \ + for (p = (s) + strlen(s); p > (s) && ISSPACE(p[-1]); p--); \ + *p = 0; \ + } + + SKIP(buf, np, ISSPACE(*np)); /* find name begin */ + if (*np == 0) + return ("missing attribute name"); + SKIP(np, ep, !ISSPACE(*ep) && *ep != '='); /* find name end */ + SKIP(ep, cp, ISSPACE(*cp)); /* skip blanks before '=' */ + if (*cp != '=') /* need '=' */ + return ("missing '=' after attribute name"); + *ep = 0; /* terminate name */ + cp++; /* skip over '=' */ + SKIP(cp, vp, ISSPACE(*vp)); /* skip leading blanks */ + TRIM(vp); /* trim trailing blanks */ + *name = np; + *value = vp; + return (0); +} diff --git a/gnu/dist/postfix/src/util/stream_trigger.c b/gnu/dist/postfix/src/util/stream_trigger.c index 585f392bf6e..9cb57e5135e 100644 --- a/gnu/dist/postfix/src/util/stream_trigger.c +++ b/gnu/dist/postfix/src/util/stream_trigger.c @@ -15,6 +15,10 @@ /* stream_trigger() wakes up the named stream server by making /* a brief connection to it and writing the named buffer. /* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* /* Arguments: /* .IP service /* Name of the communication endpoint. @@ -51,13 +55,41 @@ #include <msg.h> #include <connect.h> #include <iostuff.h> +#include <mymalloc.h> +#include <events.h> #include <trigger.h> +struct stream_trigger { + int fd; + char *service; +}; + +/* stream_trigger_event - disconnect from peer */ + +static void stream_trigger_event(int event, char *context) +{ + struct stream_trigger *sp = (struct stream_trigger *) context; + static char *myname = "stream_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, sp->service); + event_disable_readwrite(sp->fd); + event_cancel_timer(stream_trigger_event, context); + if (close(sp->fd) < 0) + msg_warn("%s: close %s: %m", myname, sp->service); + myfree(sp->service); + myfree((char *) sp); +} + /* stream_trigger - wakeup stream server */ int stream_trigger(const char *service, const char *buf, int len, int timeout) { char *myname = "stream_trigger"; + struct stream_trigger *sp; int fd; if (msg_verbose > 1) @@ -71,19 +103,28 @@ int stream_trigger(const char *service, const char *buf, int len, int timeou msg_warn("%s: connect to %s: %m", myname, service); return (-1); } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + sp = (struct stream_trigger *) mymalloc(sizeof(*sp)); + sp->fd = fd; + sp->service = mystrdup(service); /* * Write the request... */ - if (write_buf(fd, buf, len, timeout) < 0) + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) if (msg_verbose) msg_warn("%s: write to %s: %m", myname, service); /* - * Disconnect. + * Wakeup when the peer disconnects, or when we lose patience. */ - if (close(fd) < 0) - if (msg_verbose) - msg_warn("%s: close %s: %m", myname, service); + if (timeout > 0) + event_request_timer(stream_trigger_event, (char *) sp, timeout + 100); + event_enable_read(fd, stream_trigger_event, (char *) sp); return (0); } diff --git a/gnu/dist/postfix/src/util/stringops.h b/gnu/dist/postfix/src/util/stringops.h index 1310d4cb062..0cd80c797d1 100644 --- a/gnu/dist/postfix/src/util/stringops.h +++ b/gnu/dist/postfix/src/util/stringops.h @@ -30,6 +30,8 @@ extern char *translit(char *, const char *, const char *); extern char *basename(const char *); #endif extern VSTRING *unescape(VSTRING *, const char *); +extern int alldig(const char *); +extern const char *split_nameval(char *, char **, char **); /* LICENSE /* .ad diff --git a/gnu/dist/postfix/src/util/unix_trigger.c b/gnu/dist/postfix/src/util/unix_trigger.c index d0f03d6c78f..6a3b1916073 100644 --- a/gnu/dist/postfix/src/util/unix_trigger.c +++ b/gnu/dist/postfix/src/util/unix_trigger.c @@ -15,6 +15,10 @@ /* unix_trigger() wakes up the named UNIX-domain server by making /* a brief connection to it and writing the named buffer. /* +/* The connection is closed by a background thread. Some kernels +/* cannot handle client-side disconnect before the server has +/* received the message. +/* /* Arguments: /* .IP service /* Name of the communication endpoint. @@ -43,6 +47,7 @@ /* System library. */ #include <sys_defs.h> +#include <sys/socket.h> #include <unistd.h> #include <string.h> @@ -51,13 +56,41 @@ #include <msg.h> #include <connect.h> #include <iostuff.h> +#include <mymalloc.h> +#include <events.h> #include <trigger.h> +struct unix_trigger { + int fd; + char *service; +}; + +/* unix_trigger_event - disconnect from peer */ + +static void unix_trigger_event(int event, char *context) +{ + struct unix_trigger *up = (struct unix_trigger *) context; + static char *myname = "unix_trigger_event"; + + /* + * Disconnect. + */ + if (event == EVENT_TIME) + msg_warn("%s: read timeout for service %s", myname, up->service); + event_disable_readwrite(up->fd); + event_cancel_timer(unix_trigger_event, context); + if (close(up->fd) < 0) + msg_warn("%s: close %s: %m", myname, up->service); + myfree(up->service); + myfree((char *) up); +} + /* unix_trigger - wakeup UNIX-domain server */ int unix_trigger(const char *service, const char *buf, int len, int timeout) { char *myname = "unix_trigger"; + struct unix_trigger *up; int fd; if (msg_verbose > 1) @@ -71,19 +104,28 @@ int unix_trigger(const char *service, const char *buf, int len, int timeout) msg_warn("%s: connect to %s: %m", myname, service); return (-1); } + close_on_exec(fd, CLOSE_ON_EXEC); + + /* + * Stash away context. + */ + up = (struct unix_trigger *) mymalloc(sizeof(*up)); + up->fd = fd; + up->service = mystrdup(service); /* * Write the request... */ - if (write_buf(fd, buf, len, timeout) < 0) + if (write_buf(fd, buf, len, timeout) < 0 + || write_buf(fd, "", 1, timeout) < 0) if (msg_verbose) msg_warn("%s: write to %s: %m", myname, service); /* - * Disconnect. + * Wakeup when the peer disconnects, or when we lose patience. */ - if (close(fd) < 0) - if (msg_verbose) - msg_warn("%s: close %s: %m", myname, service); + if (timeout > 0) + event_request_timer(unix_trigger_event, (char *) up, timeout + 100); + event_enable_read(fd, unix_trigger_event, (char *) up); return (0); } diff --git a/gnu/dist/postfix/src/util/vstream.c b/gnu/dist/postfix/src/util/vstream.c index e2ec94ddcf7..22c59440ca7 100644 --- a/gnu/dist/postfix/src/util/vstream.c +++ b/gnu/dist/postfix/src/util/vstream.c @@ -72,6 +72,9 @@ /* int vstream_fileno(stream) /* VSTREAM *stream; /* +/* void *vstream_context(stream) +/* VSTREAM *stream; +/* /* int vstream_ferror(stream) /* VSTREAM *stream; /* @@ -97,7 +100,7 @@ /* int vstream_setjmp(stream) /* VSTREAM *stream; /* -/* void longjmp(stream, val) +/* void vstream_longjmp(stream, val) /* VSTREAM *stream; /* int val; /* @@ -175,11 +178,11 @@ /* /* VSTREAM_PUTCHAR(c) is an alias for VSTREAM_PUTC(c, VSTREAM_OUT). /* -/* vstream_unget() pushes back a character onto the specified stream +/* vstream_ungetc() pushes back a character onto the specified stream /* and returns the character, or VSTREAM_EOF in case of problems. /* It is an error to push back before reading (or immediately after /* changing the stream offset via vstream_fseek()). Upon successful -/* return, vstream_unget() clears the end-of-file stream flag. +/* return, vstream_ungetc() clears the end-of-file stream flag. /* /* vstream_fputs() appends the given null-terminated string to the /* specified buffered stream. The result is 0 in case of success, @@ -250,6 +253,9 @@ /* a buffered stream. With streams that have separate read/write /* file descriptors, the result is the current descriptor. /* +/* vstream_context() returns the application context that is passed on to +/* the application-specified read/write routines. +/* /* VSTREAM_PATH() is an unsafe macro that returns the name stored /* with vstream_fopen() or with vstream_control(). The macro is /* unsafe because it evaluates some arguments more than once. diff --git a/gnu/dist/postfix/src/util/vstream.h b/gnu/dist/postfix/src/util/vstream.h index f38259b2701..3ec6382956b 100644 --- a/gnu/dist/postfix/src/util/vstream.h +++ b/gnu/dist/postfix/src/util/vstream.h @@ -90,6 +90,7 @@ extern VSTREAM *vstream_fdopen(int, int); #define VSTREAM_GETCHAR() VSTREAM_GETC(VSTREAM_IN) #define vstream_fileno(vp) ((vp)->fd) +#define vstream_context(vp) ((vp)->context) #define vstream_ferror(vp) vbuf_error(&(vp)->buf) #define vstream_feof(vp) vbuf_eof(&(vp)->buf) #define vstream_ftimeout(vp) vbuf_timeout(&(vp)->buf) diff --git a/gnu/dist/postfix/src/util/vstring.c b/gnu/dist/postfix/src/util/vstring.c index 356b114b2b8..0f3e33b5e40 100644 --- a/gnu/dist/postfix/src/util/vstring.c +++ b/gnu/dist/postfix/src/util/vstring.c @@ -67,6 +67,16 @@ /* const char *src; /* int len; /* +/* VSTRING *vstring_memcpy(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* int len; +/* +/* VSTRING *vstring_memcat(vp, src, len) +/* VSTRING *vp; +/* const char *src; +/* int len; +/* /* VSTRING *vstring_sprintf(vp, format, ...) /* VSTRING *vp; /* const char *format; @@ -101,14 +111,15 @@ /* of at least "len" bytes. The minimal length is 1. The result /* is a null-terminated string of length zero. /* -/* vstring_ctl() gives control over memory management policy. +/* vstring_ctl() gives additional control over vstring behavior. /* The function takes a VSTRING pointer and a list of zero -/* or more (name,value) pairs. The expected valye type of the -/* value depends on the specified name. The name codes are: +/* or more (name,value) pairs. The expected value type +/* depends on the specified name. The value name codes are: /* .IP "VSTRING_CTL_MAXLEN (int)" /* Specifies a hard upper limit on a string's length. When the /* length would be exceeded, the program simulates a memory /* allocation problem (i.e. it terminates through msg_fatal()). +/* This fuctionality is currently unimplemented. /* .IP "VSTRING_CTL_END (no value)" /* Specifies the end of the argument list. Forgetting to terminate /* the argument list may cause the program to crash. @@ -174,6 +185,14 @@ /* vstring_strncat() copies at most \fIlen\fR characters. Otherwise it is /* identical to vstring_strcat(). /* +/* vstring_memcpy() copies \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* +/* vstring_memcat() appends \fIlen\fR bytes to a variable-length string. +/* \fIsrc\fP provides the data to be copied; \fIvp\fP is the +/* target and result value. The result is not null-terminated. +/* /* vstring_sprintf() produces a formatted string according to its /* \fIformat\fR argument. See vstring_vsprintf() for details. /* @@ -392,6 +411,29 @@ VSTRING *vstring_strncat(VSTRING *vp, const char *src, int len) return (vp); } +/* vstring_memcpy - copy buffer of limited length */ + +VSTRING *vstring_memcpy(VSTRING *vp, const char *src, int len) +{ + VSTRING_RESET(vp); + + VSTRING_SPACE(vp, len); + memcpy(vstring_str(vp), src, len); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + +/* vstring_memcat - append buffer of limited length */ + +VSTRING *vstring_memcat(VSTRING *vp, const char *src, int len) +{ + VSTRING_SPACE(vp, len); + memcpy(vstring_end(vp), src, len); + len += VSTRING_LEN(vp); + VSTRING_AT_OFFSET(vp, len); + return (vp); +} + /* vstring_export - VSTRING to bare string */ char *vstring_export(VSTRING *vp) diff --git a/gnu/dist/postfix/src/util/vstring.h b/gnu/dist/postfix/src/util/vstring.h index 6ae8947e784..b1b81bb8acd 100644 --- a/gnu/dist/postfix/src/util/vstring.h +++ b/gnu/dist/postfix/src/util/vstring.h @@ -38,6 +38,8 @@ extern VSTRING *vstring_strcpy(VSTRING *, const char *); extern VSTRING *vstring_strncpy(VSTRING *, const char *, int); extern VSTRING *vstring_strcat(VSTRING *, const char *); extern VSTRING *vstring_strncat(VSTRING *, const char *, int); +extern VSTRING *vstring_memcpy(VSTRING *, const char *, int); +extern VSTRING *vstring_memcat(VSTRING *, const char *, int); extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf(VSTRING *, const char *,...); extern VSTRING *PRINTFLIKE(2, 3) vstring_sprintf_append(VSTRING *, const char *,...); extern char *vstring_export(VSTRING *); diff --git a/gnu/dist/postfix/src/util/vstring_vstream.c b/gnu/dist/postfix/src/util/vstring_vstream.c index 078d255e1ac..2f644bf1dc7 100644 --- a/gnu/dist/postfix/src/util/vstring_vstream.c +++ b/gnu/dist/postfix/src/util/vstring_vstream.c @@ -27,6 +27,11 @@ /* VSTRING *vp; /* VSTREAM *fp; /* int bound; +/* +/* int vstring_get_null_bound(vp, fp, bound) +/* VSTRING *vp; +/* VSTREAM *fp; +/* int bound; /* DESCRIPTION /* The routines in this module each read one newline or null-terminated /* string from an input stream. In all cases the result is either the @@ -41,7 +46,7 @@ /* vstring_get_null() reads a null-terminated string from the named /* stream. /* -/* vstring_get_bound() and vstring_get_nonl_bound() read no more +/* the vstring_get<whatever>_bound() routines read no more /* than \fIbound\fR characters. Otherwise they behave like the /* unbounded versions documented above. /* DIAGNOSTICS @@ -154,6 +159,22 @@ int vstring_get_nonl_bound(VSTRING *vp, VSTREAM *fp, int bound) return (c == '\n' ? c : VSTRING_GET_RESULT(vp)); } +/* vstring_get_null_bound - read null-terminated string from file */ + +int vstring_get_null_bound(VSTRING *vp, VSTREAM *fp, int bound) +{ + int c; + + if (bound <= 0) + msg_panic("vstring_get_nonl_bound: invalid bound %d", bound); + + VSTRING_RESET(vp); + while (bound-- > 0 && (c = VSTREAM_GETC(fp)) != VSTREAM_EOF && c != 0) + VSTRING_ADDCH(vp, c); + VSTRING_TERMINATE(vp); + return (c == 0 ? c : VSTRING_GET_RESULT(vp)); +} + #ifdef TEST /* diff --git a/gnu/dist/postfix/src/util/vstring_vstream.h b/gnu/dist/postfix/src/util/vstring_vstream.h index 897167ca640..68bd8106db0 100644 --- a/gnu/dist/postfix/src/util/vstring_vstream.h +++ b/gnu/dist/postfix/src/util/vstring_vstream.h @@ -24,6 +24,7 @@ extern int vstring_get_nonl(VSTRING *, VSTREAM *); extern int vstring_get_null(VSTRING *, VSTREAM *); extern int vstring_get_bound(VSTRING *, VSTREAM *, int); extern int vstring_get_nonl_bound(VSTRING *, VSTREAM *, int); +extern int vstring_get_null_bound(VSTRING *, VSTREAM *, int); /* * Backwards compatibility for code that still uses the vstring_fgets() diff --git a/gnu/dist/postfix/src/virtual/.printfck b/gnu/dist/postfix/src/virtual/.printfck new file mode 100644 index 00000000000..66016ed453c --- /dev/null +++ b/gnu/dist/postfix/src/virtual/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/gnu/dist/postfix/src/virtual/Makefile.in b/gnu/dist/postfix/src/virtual/Makefile.in new file mode 100644 index 00000000000..8bf523ed3f8 --- /dev/null +++ b/gnu/dist/postfix/src/virtual/Makefile.in @@ -0,0 +1,166 @@ +SHELL = /bin/sh +SRCS = virtual.c mailbox.c recipient.c deliver_attr.c maildir.c unknown.c +OBJS = virtual.o mailbox.o recipient.o deliver_attr.o maildir.o unknown.o +HDRS = virtual.h +TESTSRC = +WARN = -W -Wformat -Wimplicit -Wmissing-prototypes \ + -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \ + -Wunused +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) -I.. +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +PROG = virtual +TESTPROG= +INC_DIR = ../../include +LIBS = ../../lib/libmaster.a ../../lib/libglobal.a ../../lib/libutil.a $(AUXLIBS) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +Makefile: Makefile.in + (set -e; echo "# DO NOT EDIT"; $(OPTS) $(SHELL) ../../makedefs; cat $?) >$@ + +test: $(TESTPROG) + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + cp *.h printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' -e 'p' -e '}'; \ + done) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @make -f Makefile.in Makefile + +# do not edit below this line - it is generated by 'make depend' +deliver_attr.o: deliver_attr.c +deliver_attr.o: ../../include/sys_defs.h +deliver_attr.o: ../../include/msg.h +deliver_attr.o: ../../include/vstream.h +deliver_attr.o: ../../include/vbuf.h +deliver_attr.o: virtual.h +deliver_attr.o: ../../include/vstring.h +deliver_attr.o: ../../include/deliver_request.h +deliver_attr.o: ../../include/recipient_list.h +deliver_attr.o: ../../include/maps.h +deliver_attr.o: ../../include/dict.h +deliver_attr.o: ../../include/argv.h +deliver_attr.o: ../../include/mbox_conf.h +mailbox.o: mailbox.c +mailbox.o: ../../include/sys_defs.h +mailbox.o: ../../include/msg.h +mailbox.o: ../../include/vstring.h +mailbox.o: ../../include/vbuf.h +mailbox.o: ../../include/vstream.h +mailbox.o: ../../include/mymalloc.h +mailbox.o: ../../include/stringops.h +mailbox.o: ../../include/set_eugid.h +mailbox.o: ../../include/mail_copy.h +mailbox.o: ../../include/mbox_open.h +mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/defer.h +mailbox.o: ../../include/bounce.h +mailbox.o: ../../include/sent.h +mailbox.o: ../../include/mail_params.h +mailbox.o: virtual.h +mailbox.o: ../../include/deliver_request.h +mailbox.o: ../../include/recipient_list.h +mailbox.o: ../../include/maps.h +mailbox.o: ../../include/dict.h +mailbox.o: ../../include/argv.h +mailbox.o: ../../include/mbox_conf.h +maildir.o: maildir.c +maildir.o: ../../include/sys_defs.h +maildir.o: ../../include/msg.h +maildir.o: ../../include/mymalloc.h +maildir.o: ../../include/stringops.h +maildir.o: ../../include/vstring.h +maildir.o: ../../include/vbuf.h +maildir.o: ../../include/vstream.h +maildir.o: ../../include/make_dirs.h +maildir.o: ../../include/set_eugid.h +maildir.o: ../../include/get_hostname.h +maildir.o: ../../include/sane_fsops.h +maildir.o: ../../include/mail_copy.h +maildir.o: ../../include/bounce.h +maildir.o: ../../include/defer.h +maildir.o: ../../include/sent.h +maildir.o: ../../include/mail_params.h +maildir.o: virtual.h +maildir.o: ../../include/deliver_request.h +maildir.o: ../../include/recipient_list.h +maildir.o: ../../include/maps.h +maildir.o: ../../include/dict.h +maildir.o: ../../include/argv.h +maildir.o: ../../include/mbox_conf.h +recipient.o: recipient.c +recipient.o: ../../include/sys_defs.h +recipient.o: ../../include/msg.h +recipient.o: ../../include/mymalloc.h +recipient.o: ../../include/stringops.h +recipient.o: ../../include/vstring.h +recipient.o: ../../include/vbuf.h +recipient.o: ../../include/bounce.h +recipient.o: virtual.h +recipient.o: ../../include/vstream.h +recipient.o: ../../include/deliver_request.h +recipient.o: ../../include/recipient_list.h +recipient.o: ../../include/maps.h +recipient.o: ../../include/dict.h +recipient.o: ../../include/argv.h +recipient.o: ../../include/mbox_conf.h +unknown.o: unknown.c +unknown.o: ../../include/sys_defs.h +unknown.o: ../../include/msg.h +unknown.o: ../../include/bounce.h +unknown.o: virtual.h +unknown.o: ../../include/vstream.h +unknown.o: ../../include/vbuf.h +unknown.o: ../../include/vstring.h +unknown.o: ../../include/deliver_request.h +unknown.o: ../../include/recipient_list.h +unknown.o: ../../include/maps.h +unknown.o: ../../include/dict.h +unknown.o: ../../include/argv.h +unknown.o: ../../include/mbox_conf.h +virtual.o: virtual.c +virtual.o: ../../include/sys_defs.h +virtual.o: ../../include/msg.h +virtual.o: ../../include/vstring.h +virtual.o: ../../include/vbuf.h +virtual.o: ../../include/vstream.h +virtual.o: ../../include/iostuff.h +virtual.o: ../../include/set_eugid.h +virtual.o: ../../include/dict.h +virtual.o: ../../include/argv.h +virtual.o: ../../include/mail_queue.h +virtual.o: ../../include/recipient_list.h +virtual.o: ../../include/deliver_request.h +virtual.o: ../../include/deliver_completed.h +virtual.o: ../../include/mail_params.h +virtual.o: ../../include/mail_conf.h +virtual.o: ../../include/mail_server.h +virtual.o: virtual.h +virtual.o: ../../include/maps.h +virtual.o: ../../include/mbox_conf.h diff --git a/gnu/dist/postfix/src/virtual/deliver_attr.c b/gnu/dist/postfix/src/virtual/deliver_attr.c new file mode 100644 index 00000000000..63b15580fcf --- /dev/null +++ b/gnu/dist/postfix/src/virtual/deliver_attr.c @@ -0,0 +1,74 @@ +/*++ +/* NAME +/* deliver_attr 3 +/* SUMMARY +/* initialize message delivery attributes +/* SYNOPSIS +/* #include "virtual.h" +/* +/* void deliver_attr_init(attrp) +/* DELIVER_ATTR *attrp; +/* +/* void deliver_attr_dump(attrp) +/* DELIVER_ATTR *attrp; +/* DESCRIPTION +/* deliver_attr_init() initializes a structure with message delivery +/* attributes to a known initial state (all zeros). +/* +/* deliver_attr_dump() logs the contents of the given attribute list. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstream.h> + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_attr_init - set message delivery attributes to all-zero state */ + +void deliver_attr_init(DELIVER_ATTR *attrp) +{ + attrp->level = 0; + attrp->fp = 0; + attrp->queue_name = 0; + attrp->queue_id = 0; + attrp->offset = 0; + attrp->sender = 0; + attrp->recipient = 0; + attrp->user = 0; + attrp->delivered = 0; + attrp->relay = 0; +} + +/* deliver_attr_dump - log message delivery attributes */ + +void deliver_attr_dump(DELIVER_ATTR *attrp) +{ + msg_info("level: %d", attrp->level); + msg_info("path: %s", VSTREAM_PATH(attrp->fp)); + msg_info("fp: 0x%lx", (long) attrp->fp); + msg_info("queue_name: %s", attrp->queue_name ? attrp->queue_name : "null"); + msg_info("queue_id: %s", attrp->queue_id ? attrp->queue_id : "null"); + msg_info("offset: %ld", attrp->offset); + msg_info("sender: %s", attrp->sender ? attrp->sender : "null"); + msg_info("recipient: %s", attrp->recipient ? attrp->recipient : "null"); + msg_info("user: %s", attrp->user ? attrp->user : "null"); + msg_info("delivered: %s", attrp->delivered ? attrp->delivered : "null"); + msg_info("relay: %s", attrp->relay ? attrp->relay : "null"); +} diff --git a/gnu/dist/postfix/src/virtual/mailbox.c b/gnu/dist/postfix/src/virtual/mailbox.c new file mode 100644 index 00000000000..21782404b62 --- /dev/null +++ b/gnu/dist/postfix/src/virtual/mailbox.c @@ -0,0 +1,246 @@ +/*++ +/* NAME +/* mailbox 3 +/* SUMMARY +/* mailbox delivery +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_mailbox(state, usr_attr, statusp) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* int *statusp; +/* DESCRIPTION +/* deliver_mailbox() delivers to UNIX-style mailbox or to maildir. +/* +/* A zero result means that the named user was not found. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* .IP statusp +/* Delivery status: see below. +/* DIAGNOSTICS +/* The message delivery status is non-zero when delivery should be tried +/* again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <mymalloc.h> +#include <stringops.h> +#include <set_eugid.h> + +/* Global library. */ + +#include <mail_copy.h> +#include <mbox_open.h> +#include <defer.h> +#include <sent.h> +#include <mail_params.h> + +#ifndef EDQUOT +#define EDQUOT EFBIG +#endif + +/* Application-specific. */ + +#include "virtual.h" + +#define YES 1 +#define NO 0 + +/* deliver_mailbox_file - deliver to recipient mailbox */ + +static int deliver_mailbox_file(LOCAL_STATE state, USER_ATTR usr_attr) +{ + char *myname = "deliver_mailbox_file"; + VSTRING *why; + MBOX *mp; + int mail_copy_status; + int deliver_status; + int copy_flags; + long end; + struct stat st; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + state.msg_attr.delivered = state.msg_attr.recipient; + mail_copy_status = MAIL_COPY_STAT_WRITE; + why = vstring_alloc(100); + + /* + * Lock the mailbox and open/create the mailbox file. + * + * Write the file as the recipient, so that file quota work. + */ + copy_flags = MAIL_COPY_MBOX; + + set_eugid(usr_attr.uid, usr_attr.gid); + mp = mbox_open(usr_attr.mailbox, O_APPEND | O_WRONLY | O_CREAT, + S_IRUSR | S_IWUSR, &st, -1, -1, + virtual_mbox_lock_mask, why); + if (mp != 0) { + if (S_ISREG(st.st_mode) == 0) { + vstream_fclose(mp->fp); + vstring_sprintf(why, "destination is not a regular file"); + errno = 0; + } else { + end = vstream_fseek(mp->fp, (off_t) 0, SEEK_END); + mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), mp->fp, + copy_flags, "\n", why); + } + mbox_release(mp); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * As the mail system, bounce, defer delivery, or report success. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + deliver_status = (errno == EDQUOT || errno == EFBIG ? + bounce_append : defer_append) + (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "mailbox %s: %s", usr_attr.mailbox, vstring_str(why)); + } else { + deliver_status = sent(SENT_ATTR(state.msg_attr), "mailbox"); + } + vstring_free(why); + return (deliver_status); +} + +/* deliver_mailbox - deliver to recipient mailbox */ + +int deliver_mailbox(LOCAL_STATE state, USER_ATTR usr_attr, int *statusp) +{ + char *myname = "deliver_mailbox"; + const char *mailbox_res; + const char *uid_res; + const char *gid_res; + long n; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Sanity check. + */ + if (*var_virt_mailbox_base != '/') + msg_fatal("do not specify relative pathname: %s = %s", + VAR_VIRT_MAILBOX_BASE, var_virt_mailbox_base); + + /* + * Look up the mailbox location. Bounce if not found, defer in case of + * trouble. + */ + mailbox_res = maps_find(virtual_mailbox_maps, state.msg_attr.user, + DICT_FLAG_FIXED); + if (mailbox_res == 0) { + if (dict_errno == 0) + return (NO); + + *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "%s: lookup %s: %m", + virtual_mailbox_maps->title, state.msg_attr.user); + return (YES); + } + usr_attr.mailbox = concatenate(var_virt_mailbox_base, "/", + mailbox_res, (char *) 0); + +#define RETURN(res) { myfree(usr_attr.mailbox); return (res); } + + /* + * Look up the mailbox owner rights. Defer in case of trouble. + */ + if ((uid_res = maps_find(virtual_uid_maps, state.msg_attr.user, + DICT_FLAG_FIXED)) == 0) { + *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "recipient %s: uid not found in %s", + state.msg_attr.user, virtual_uid_maps->title); + RETURN(YES); + } + if ((n = atol(uid_res)) < var_virt_minimum_uid) { + *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "recipient %s: bad uid %s in %s", + state.msg_attr.user, uid_res, virtual_uid_maps->title); + RETURN(YES); + } + usr_attr.uid = (uid_t) n; + + /* + * Look up the mailbox group rights. Defer in case of trouble. + */ + if ((gid_res = maps_find(virtual_gid_maps, state.msg_attr.user, + DICT_FLAG_FIXED)) == 0) { + *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "recipient %s: gid not found in %s", + state.msg_attr.user, virtual_gid_maps->title); + RETURN(YES); + } + if ((n = atol(gid_res)) <= 0) { + *statusp = defer_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "recipient %s: bad gid %s in %s", + state.msg_attr.user, gid_res, virtual_gid_maps->title); + RETURN(YES); + } + usr_attr.gid = (gid_t) n; + + if (msg_verbose) + msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", + myname, state.level, usr_attr.mailbox, + (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); + + /* + * Deliver to mailbox or to external command. + */ +#define LAST_CHAR(s) (s[strlen(s) - 1]) + + if (LAST_CHAR(usr_attr.mailbox) == '/') + *statusp = deliver_maildir(state, usr_attr); + else + *statusp = deliver_mailbox_file(state, usr_attr); + + /* + * Cleanup. + */ + RETURN(YES); +} diff --git a/gnu/dist/postfix/src/virtual/maildir.c b/gnu/dist/postfix/src/virtual/maildir.c new file mode 100644 index 00000000000..2db4130020a --- /dev/null +++ b/gnu/dist/postfix/src/virtual/maildir.c @@ -0,0 +1,176 @@ +/*++ +/* NAME +/* maildir 3 +/* SUMMARY +/* delivery to maildir +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_maildir(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR usr_attr; +/* DESCRIPTION +/* deliver_maildir() delivers a message to a qmail-style maildir. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, recipient and more. +/* .IP usr_attr +/* Attributes describing user rights and environment information. +/* DIAGNOSTICS +/* deliver_maildir() always succeeds or it bounces the message. +/* SEE ALSO +/* bounce(3) +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include "sys_defs.h" +#include <errno.h> + +#ifndef EDQUOT +#define EDQUOT EFBIG +#endif + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> +#include <vstream.h> +#include <vstring.h> +#include <make_dirs.h> +#include <set_eugid.h> +#include <get_hostname.h> +#include <sane_fsops.h> + +/* Global library. */ + +#include <mail_copy.h> +#include <bounce.h> +#include <defer.h> +#include <sent.h> +#include <mail_params.h> + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_maildir - delivery to maildir-style mailbox */ + +int deliver_maildir(LOCAL_STATE state, USER_ATTR usr_attr) +{ + char *myname = "deliver_maildir"; + char *newdir; + char *tmpdir; + char *curdir; + char *tmpfile; + char *newfile; + VSTRING *why; + VSTRING *buf; + VSTREAM *dst; + int mail_copy_status; + int deliver_status; + int copy_flags; + static int count; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Initialize. Assume the operation will fail. Set the delivered + * attribute to reflect the final recipient. + */ + if (vstream_fseek(state.msg_attr.fp, state.msg_attr.offset, SEEK_SET) < 0) + msg_fatal("seek message file %s: %m", VSTREAM_PATH(state.msg_attr.fp)); + state.msg_attr.delivered = state.msg_attr.recipient; + mail_copy_status = MAIL_COPY_STAT_WRITE; + buf = vstring_alloc(100); + why = vstring_alloc(100); + + copy_flags = MAIL_COPY_TOFILE | MAIL_COPY_RETURN_PATH | MAIL_COPY_DELIVERED; + + newdir = concatenate(usr_attr.mailbox, "new/", (char *) 0); + tmpdir = concatenate(usr_attr.mailbox, "tmp/", (char *) 0); + curdir = concatenate(usr_attr.mailbox, "cur/", (char *) 0); + + /* + * Create and write the file as the recipient, so that file quota work. + * Create any missing directories on the fly. The file name is chosen + * according to ftp://koobera.math.uic.edu/www/proto/maildir.html: + * + * "A unique name has three pieces, separated by dots. On the left is the + * result of time(). On the right is the result of gethostname(). In the + * middle is something that doesn't repeat within one second on a single + * host. I fork a new process for each delivery, so I just use the + * process ID. If you're delivering several messages from one process, + * use starttime.pid_count.host, where starttime is the time that your + * process started, and count is the number of messages you've + * delivered." + */ +#define STR vstring_str + + set_eugid(usr_attr.uid, usr_attr.gid); + vstring_sprintf(buf, "%ld.%d_%d.%s", (long) var_starttime, + var_pid, count++, get_hostname()); + tmpfile = concatenate(tmpdir, STR(buf), (char *) 0); + newfile = concatenate(newdir, STR(buf), (char *) 0); + if ((dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0 + && (errno != ENOENT + || make_dirs(tmpdir, 0700) < 0 + || (dst = vstream_fopen(tmpfile, O_WRONLY | O_CREAT | O_EXCL, 0600)) == 0)) { + vstring_sprintf(why, "create %s: %m", tmpfile); + } else { + if ((mail_copy_status = mail_copy(COPY_ATTR(state.msg_attr), + dst, copy_flags, "\n", why)) == 0) { + if (sane_link(tmpfile, newfile) < 0 + && (errno != ENOENT + || (make_dirs(curdir, 0700), make_dirs(newdir, 0700)) < 0 + || sane_link(tmpfile, newfile) < 0)) { + vstring_sprintf(why, "link to %s: %m", newfile); + mail_copy_status = MAIL_COPY_STAT_WRITE; + } + } + if (unlink(tmpfile) < 0) + msg_warn("remove %s: %m", tmpfile); + } + set_eugid(var_owner_uid, var_owner_gid); + + /* + * The maildir location is controlled by the mail administrator. If + * delivery fails, try again later. We would just bounce when the maildir + * location possibly under user control. + */ + if (mail_copy_status & MAIL_COPY_STAT_CORRUPT) { + deliver_status = DEL_STAT_DEFER; + } else if (mail_copy_status != 0) { + deliver_status = (errno == EDQUOT || errno == EFBIG ? + bounce_append : defer_append) + (BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "maildir delivery failed: %s", vstring_str(why)); + + } else { + deliver_status = sent(SENT_ATTR(state.msg_attr), "maildir"); + } + vstring_free(buf); + vstring_free(why); + myfree(newdir); + myfree(tmpdir); + myfree(curdir); + myfree(tmpfile); + myfree(newfile); + return (deliver_status); +} diff --git a/gnu/dist/postfix/src/virtual/recipient.c b/gnu/dist/postfix/src/virtual/recipient.c new file mode 100644 index 00000000000..725108f5be7 --- /dev/null +++ b/gnu/dist/postfix/src/virtual/recipient.c @@ -0,0 +1,93 @@ +/*++ +/* NAME +/* recipient 3 +/* SUMMARY +/* deliver to one local recipient +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_recipient(state, usr_attr) +/* LOCAL_STATE state; +/* USER_ATTR *usr_attr; +/* DESCRIPTION +/* deliver_recipient() delivers a message to a local recipient. +/* +/* Arguments: +/* .IP state +/* The attributes that specify the message, sender, and more. +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* DIAGNOSTICS +/* deliver_recipient() returns non-zero when delivery should be +/* tried again. +/* SEE ALSO +/* mailbox(3) delivery to UNIX-style mailbox +/* maildir(3) delivery to qmail-style maildir +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <stringops.h> + +/* Global library. */ + +#include <bounce.h> + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_recipient - deliver one local recipient */ + +int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) +{ + char *myname = "deliver_recipient"; + int rcpt_stat; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + /* + * Set up the recipient-specific attributes. The recipient's lookup + * handle is the full address. + */ + if (state.msg_attr.delivered == 0) + state.msg_attr.delivered = state.msg_attr.recipient; + state.msg_attr.user = mystrdup(state.msg_attr.recipient); + lowercase(state.msg_attr.user); + + /* + * Deliver + */ + if (msg_verbose) + deliver_attr_dump(&state.msg_attr); + + if (deliver_mailbox(state, usr_attr, &rcpt_stat) == 0) + rcpt_stat = deliver_unknown(state); + + /* + * Cleanup. + */ + myfree(state.msg_attr.user); + + return (rcpt_stat); +} diff --git a/gnu/dist/postfix/src/virtual/unknown.c b/gnu/dist/postfix/src/virtual/unknown.c new file mode 100644 index 00000000000..8989046e0d1 --- /dev/null +++ b/gnu/dist/postfix/src/virtual/unknown.c @@ -0,0 +1,64 @@ +/*++ +/* NAME +/* unknown 3 +/* SUMMARY +/* delivery of unknown recipients +/* SYNOPSIS +/* #include "virtual.h" +/* +/* int deliver_unknown(state) +/* LOCAL_STATE state; +/* DESCRIPTION +/* deliver_unknown() delivers a message for unknown recipients. +/* .PP +/* Arguments: +/* .IP state +/* Message delivery attributes (sender, recipient etc.). +/* .IP usr_attr +/* Attributes describing user rights and mailbox location. +/* DIAGNOSTICS +/* The result status is non-zero when delivery should be tried again. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> + +/* Utility library. */ + +#include <msg.h> + +/* Global library. */ + +#include <bounce.h> + +/* Application-specific. */ + +#include "virtual.h" + +/* deliver_unknown - delivery for unknown recipients */ + +int deliver_unknown(LOCAL_STATE state) +{ + char *myname = "deliver_unknown"; + + /* + * Make verbose logging easier to understand. + */ + state.level++; + if (msg_verbose) + MSG_LOG_STATE(myname, state); + + return (bounce_append(BOUNCE_FLAG_KEEP, BOUNCE_ATTR(state.msg_attr), + "unknown user: \"%s\"", state.msg_attr.user)); + +} diff --git a/gnu/dist/postfix/src/virtual/virtual.c b/gnu/dist/postfix/src/virtual/virtual.c new file mode 100644 index 00000000000..7968dcc2856 --- /dev/null +++ b/gnu/dist/postfix/src/virtual/virtual.c @@ -0,0 +1,414 @@ +/*++ +/* NAME +/* virtual 8 +/* SUMMARY +/* Postfix virtual domain mail delivery agent +/* SYNOPSIS +/* \fBvirtual\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The \fBvirtual\fR delivery agent is designed for virtual mail +/* hosting services. Originally based on the Postfix local delivery +/* agent, this agent looks up recipients with map lookups of their +/* full recipient address, instead of using hard-coded unix password +/* file lookups of the address local part only. +/* +/* This delivery agent only delivers mail. Other features such as +/* mail forwarding, out-of-office notifications, etc., must be +/* configured via virtual maps or via similar lookup mechanisms. +/* MAILBOX LOCATION +/* .ad +/* .fi +/* The mailbox location is controlled by the \fBvirtual_mailbox_base\fR +/* and \fBvirtual_mailbox_maps\fR configuration parameters (see below). +/* The \fBvirtual_mailbox_maps\fR table is indexed by the full recipient +/* address. +/* +/* The mailbox pathname is constructed as follows: +/* +/* .ti +2 +/* \fB$virtual_mailbox_base/$virtual_mailbox_maps(\fIrecipient\fB)\fR +/* +/* where \fIrecipient\fR is the full recipient address. +/* UNIX MAILBOX FORMAT +/* .ad +/* .fi +/* When the mailbox location does not end in \fB/\fR, the message +/* is delivered in UNIX mailbox format. This format stores multiple +/* messages in one textfile. +/* +/* The \fBvirtual\fR delivery agent prepends a "\fBFrom \fIsender +/* time_stamp\fR" envelope header to each message, prepends a +/* \fBDelivered-To:\fR message header with the envelope recipient +/* address, prepends a \fBReturn-Path:\fR message header with the +/* envelope sender address, prepends a \fB>\fR character to lines +/* beginning with "\fBFrom \fR", and appends an empty line. +/* +/* The mailbox is locked for exclusive access while delivery is in +/* progress. In case of problems, an attempt is made to truncate the +/* mailbox to its original length. +/* QMAIL MAILDIR FORMAT +/* .ad +/* .fi +/* When the mailbox location ends in \fB/\fR, the message is delivered +/* in qmail \fBmaildir\fR format. This format stores one message per file. +/* +/* The \fBvirtual\fR delivery agent daemon prepends a \fBDelivered-To:\fR +/* message header with the envelope recipient address and prepends a +/* \fBReturn-Path:\fR message header with the envelope sender address. +/* +/* By definition, \fBmaildir\fR format does not require file locking +/* during mail delivery or retrieval. +/* MAILBOX OWNERSHIP +/* .ad +/* .fi +/* Mailbox ownership is controlled by the \fBvirtual_uid_maps\fR +/* and \fBvirtual_gid_maps\fR lookup tables, which are indexed +/* with the full recipient address. Each table provides +/* a string with the numerical user and group ID, respectively. +/* +/* The \fBvirtual_minimum_uid\fR parameter imposes a lower bound on +/* numerical user ID values that may be specified in any +/* \fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR. +/* SECURITY +/* .ad +/* .fi +/* The virtual delivery agent is not security sensitive, provided +/* that the lookup tables with recipient user/group ID information are +/* adequately protected. This program is not designed to run chrooted. +/* STANDARDS +/* RFC 822 (ARPA Internet Text Messages) +/* DIAGNOSTICS +/* Mail bounces when the recipient has no mailbox or when the +/* recipient is over disk quota. In all other cases, mail for +/* an existing recipient is deferred and a warning is logged. +/* +/* Problems and transactions are logged to \fBsyslogd\fR(8). +/* Corrupted message files are marked so that the queue +/* manager can move them to the \fBcorrupt\fR queue afterwards. +/* +/* Depending on the setting of the \fBnotify_classes\fR parameter, +/* the postmaster is notified of bounces and of other trouble. +/* BUGS +/* This delivery agent silently ignores address extensions. +/* +/* Postfix should have lookup tables that can return multiple result +/* attributes. In order to avoid the inconvenience of maintaining +/* three tables, use an LDAP or MYSQL database. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* The following \fBmain.cf\fR parameters are especially relevant to +/* this program. See the Postfix \fBmain.cf\fR file for syntax details +/* and for default values. Use the \fBpostfix reload\fR command after +/* a configuration change. +/* .SH Mailbox delivery +/* .ad +/* .fi +/* .IP \fBvirtual_mailbox_base\fR +/* Specifies a path that is prepended to all mailbox or maildir paths. +/* This is a safety measure to ensure that an out of control map in +/* \fBvirtual_mailbox_maps\fR doesn't litter the filesystem with mailboxes. +/* While it could be set to "/", this setting isn't recommended. +/* .IP "\fBvirtual_mailbox_maps\fR (regexp maps disallowed)" +/* Recipients are looked up in these maps to determine the path to +/* their mailbox or maildir. If the returned path ends in a slash +/* ("/"), maildir-style delivery is carried out, otherwise the +/* path is assumed to specify a UNIX-style mailbox file. +/* +/* Note that \fBvirtual_mailbox_base\fR is unconditionally prepended +/* to this path. +/* .IP \fBvirtual_minimum_uid\fR +/* Specifies a minimum uid that will be accepted as a return from +/* a \fBvirtual_owner_maps\fR or \fBvirtual_uid_maps\fR lookup. +/* Returned values less than this will be rejected, and the message +/* will be deferred. +/* .IP "\fBvirtual_uid_maps\fR (regexp maps disallowed)" +/* Recipients are looked up in these maps to determine the user ID to be +/* used when writing to the target mailbox. +/* .IP "\fBvirtual_gid_maps\fR (regexp maps disallowed)" +/* Recipients are looked up in these maps to determine the group ID to be +/* used when writing to the target mailbox. +/* .SH "Locking controls" +/* .ad +/* .fi +/* .IP \fBvirtual_mailbox_lock\fR +/* How to lock UNIX-style mailboxes: one or more of \fBflock\fR, +/* \fBfcntl\fR or \fBdotlock\fR. The \fBdotlock\fR method requires +/* that the recipient UID or GID has write access to the parent +/* directory of the mailbox file. +/* +/* This setting is ignored with \fBmaildir\fR style delivery, +/* because such deliveries are safe without explicit locks. +/* +/* Use the command \fBpostconf -l\fR to find out what locking methods +/* are available on your system. +/* .IP \fBdeliver_lock_attempts\fR +/* Limit the number of attempts to acquire an exclusive lock +/* on a UNIX-style mailbox file. +/* .IP \fBdeliver_lock_delay\fR +/* Time (default: seconds) between successive attempts to acquire +/* an exclusive lock on a UNIX-style mailbox file. The actual delay +/* is slightly randomized. +/* .IP \fBstale_lock_time\fR +/* Limit the time after which a stale lockfile is removed (applicable +/* to UNIX-style mailboxes only). +/* .SH "Resource controls" +/* .ad +/* .fi +/* .IP \fBvirtual_destination_concurrency_limit\fR +/* Limit the number of parallel deliveries to the same domain +/* via the \fBvirtual\fR delivery agent. +/* The default limit is taken from the +/* \fBdefault_destination_concurrency_limit\fR parameter. +/* The limit is enforced by the Postfix queue manager. +/* .IP \fBvirtual_destination_recipient_limit\fR +/* Limit the number of recipients per message delivery +/* via the \fBvirtual\fR delivery agent. +/* The default limit is taken from the +/* \fBdefault_destination_recipient_limit\fR parameter. +/* The limit is enforced by the Postfix queue manager. +/* .IP \fBvirtual_mailbox_limit\fR +/* The maximal size in bytes of a mailbox or maildir file. +/* Set to zero to disable the limit. +/* HISTORY +/* .ad +/* .fi +/* This agent was originally based on the Postfix local delivery +/* agent. Modifications mainly consisted of removing code that either +/* was not applicable or that was not safe in this context: aliases, +/* ~user/.forward files, delivery to "|command" or to /file/name. +/* +/* The \fBDelivered-To:\fR header appears in the \fBqmail\fR system +/* by Daniel Bernstein. +/* +/* The \fBmaildir\fR structure appears in the \fBqmail\fR system +/* by Daniel Bernstein. +/* SEE ALSO +/* bounce(8) non-delivery status reports +/* syslogd(8) system logging +/* qmgr(8) queue manager +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Andrew McNamara +/* andrewm@connect.com.au +/* connect.com.au Pty. Ltd. +/* Level 3, 213 Miller St +/* North Sydney 2060, NSW, Australia +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <stdlib.h> + +/* Utility library. */ + +#include <msg.h> +#include <vstring.h> +#include <vstream.h> +#include <iostuff.h> +#include <set_eugid.h> +#include <dict.h> + +/* Global library. */ + +#include <mail_queue.h> +#include <recipient_list.h> +#include <deliver_request.h> +#include <deliver_completed.h> +#include <mail_params.h> +#include <mail_conf.h> +#include <mail_params.h> + +/* Single server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + +#include "virtual.h" + + /* + * Tunable parameters. + */ +char *var_virt_mailbox_maps; +char *var_virt_uid_maps; +char *var_virt_gid_maps; +int var_virt_minimum_uid; +char *var_virt_mailbox_base; +char *var_virt_mailbox_lock; +int var_virt_mailbox_limit; + + /* + * Mappings. + */ +MAPS *virtual_mailbox_maps; +MAPS *virtual_uid_maps; +MAPS *virtual_gid_maps; + + /* + * Bit masks. + */ +int virtual_mbox_lock_mask; + +/* local_deliver - deliver message with extreme prejudice */ + +static int local_deliver(DELIVER_REQUEST *rqst, char *service) +{ + char *myname = "local_deliver"; + RECIPIENT *rcpt_end = rqst->rcpt_list.info + rqst->rcpt_list.len; + RECIPIENT *rcpt; + int rcpt_stat; + int msg_stat; + LOCAL_STATE state; + USER_ATTR usr_attr; + + if (msg_verbose) + msg_info("local_deliver: %s from %s", rqst->queue_id, rqst->sender); + + /* + * Initialize the delivery attributes that are not recipient specific. + */ + state.level = 0; + deliver_attr_init(&state.msg_attr); + state.msg_attr.queue_name = rqst->queue_name; + state.msg_attr.queue_id = rqst->queue_id; + state.msg_attr.fp = rqst->fp; + state.msg_attr.offset = rqst->data_offset; + state.msg_attr.sender = rqst->sender; + state.msg_attr.relay = service; + state.msg_attr.arrival_time = rqst->arrival_time; + RESET_USER_ATTR(usr_attr, state.level); + state.request = rqst; + + /* + * Iterate over each recipient named in the delivery request. When the + * mail delivery status for a given recipient is definite (i.e. bounced + * or delivered), update the message queue file and cross off the + * recipient. Update the per-message delivery status. + */ + for (msg_stat = 0, rcpt = rqst->rcpt_list.info; rcpt < rcpt_end; rcpt++) { + state.msg_attr.recipient = rcpt->address; + rcpt_stat = deliver_recipient(state, usr_attr); + if (rcpt_stat == 0) + deliver_completed(state.msg_attr.fp, rcpt->offset); + msg_stat |= rcpt_stat; + } + + return (msg_stat); +} + +/* local_service - perform service for client */ + +static void local_service(VSTREAM *stream, char *service, char **argv) +{ + DELIVER_REQUEST *request; + int status; + + /* + * 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 + * that is dedicated to local mail delivery service. What we see below is + * a little protocol to (1) tell the client that we are ready, (2) read a + * delivery request from the client, and (3) report the completion status + * of that request. + */ + if ((request = deliver_request_read(stream)) != 0) { + status = local_deliver(request, service); + deliver_request_done(stream, request, status); + } +} + +/* pre_accept - see if tables have changed */ + +static void pre_accept(char *unused_name, char **unused_argv) +{ + if (dict_changed()) { + msg_info("table has changed -- exiting"); + exit(0); + } +} + +/* post_init - post-jail initialization */ + +static void post_init(char *unused_name, char **unused_argv) +{ + + /* + * Drop privileges most of the time. + */ + set_eugid(var_owner_uid, var_owner_gid); + + virtual_mailbox_maps = + maps_create(VAR_VIRT_MAILBOX_MAPS, var_virt_mailbox_maps, + DICT_FLAG_LOCK); + + virtual_uid_maps = + maps_create(VAR_VIRT_UID_MAPS, var_virt_uid_maps, DICT_FLAG_LOCK); + + virtual_gid_maps = + maps_create(VAR_VIRT_GID_MAPS, var_virt_gid_maps, DICT_FLAG_LOCK); + + virtual_mbox_lock_mask = mbox_lock_mask(var_virt_mailbox_lock); +} + +/* pre_init - pre-jail initialization */ + +static void pre_init(char *unused_name, char **unused_argv) +{ + + /* + * Reset the file size limit from the message size limit to the mailbox + * size limit. + * + * We can't have mailbox size limit smaller than the message size limit, + * because that prohibits the delivery agent from updating the queue + * file. + */ + if (var_virt_mailbox_limit) { + if (var_virt_mailbox_limit < var_message_limit) + msg_fatal("main.cf configuration error: %s is smaller than %s", + VAR_VIRT_MAILBOX_LIMIT, VAR_MESSAGE_LIMIT); + set_file_limit(var_virt_mailbox_limit); + } +} + +/* main - pass control to the single-threaded skeleton */ + +int main(int argc, char **argv) +{ + static CONFIG_INT_TABLE int_table[] = { + VAR_VIRT_MINUID, DEF_VIRT_MINUID, &var_virt_minimum_uid, 1, 0, + VAR_VIRT_MAILBOX_LIMIT, DEF_VIRT_MAILBOX_LIMIT, &var_virt_mailbox_limit, 0, 0, + 0, + }; + static CONFIG_STR_TABLE str_table[] = { + VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, + VAR_VIRT_UID_MAPS, DEF_VIRT_UID_MAPS, &var_virt_uid_maps, 0, 0, + VAR_VIRT_GID_MAPS, DEF_VIRT_GID_MAPS, &var_virt_gid_maps, 0, 0, + VAR_VIRT_MAILBOX_BASE, DEF_VIRT_MAILBOX_BASE, &var_virt_mailbox_base, 0, 0, + VAR_VIRT_MAILBOX_LOCK, DEF_VIRT_MAILBOX_LOCK, &var_virt_mailbox_lock, 1, 0, + 0, + }; + + single_server_main(argc, argv, local_service, + MAIL_SERVER_INT_TABLE, int_table, + MAIL_SERVER_STR_TABLE, str_table, + MAIL_SERVER_PRE_INIT, pre_init, + MAIL_SERVER_POST_INIT, post_init, + MAIL_SERVER_PRE_ACCEPT, pre_accept, + 0); +} diff --git a/gnu/dist/postfix/src/virtual/virtual.h b/gnu/dist/postfix/src/virtual/virtual.h new file mode 100644 index 00000000000..1443bc8d04f --- /dev/null +++ b/gnu/dist/postfix/src/virtual/virtual.h @@ -0,0 +1,134 @@ +/*++ +/* NAME +/* virtual 3h +/* SUMMARY +/* virtual mail delivery +/* SYNOPSIS +/* #include "virtual.h" +/* DESCRIPTION +/* .nf + + /* + * System library. + */ +#include <unistd.h> + + /* + * Utility library. + */ +#include <vstream.h> +#include <vstring.h> + + /* + * Global library. + */ +#include <deliver_request.h> +#include <maps.h> +#include <mbox_conf.h> + + /* + * Mappings. + */ +extern MAPS *virtual_mailbox_maps; +extern MAPS *virtual_uid_maps; +extern MAPS *virtual_gid_maps; + + /* + * User attributes: these control the privileges for delivery to external + * commands, external files, or mailboxes, and the initial environment of + * external commands. + */ +typedef struct USER_ATTR { + uid_t uid; /* file/command access */ + gid_t gid; /* file/command access */ + char *mailbox; /* mailbox file or directory */ +} USER_ATTR; + + /* + * Critical macros. Not for obscurity, but to ensure consistency. + */ +#define RESET_USER_ATTR(usr_attr, level) { \ + usr_attr.uid = 0; usr_attr.gid = 0; usr_attr.mailbox = 0; \ + if (msg_verbose) \ + msg_info("%s[%d]: reset user_attr", myname, level); \ + } + + /* + * The delivery attributes are inherited from files, from aliases, and from + * whatnot. Some of the information is changed on the fly. DELIVER_ATTR + * structres are therefore passed by value, so there is no need to undo + * changes. + */ +typedef struct DELIVER_ATTR { + int level; /* recursion level */ + VSTREAM *fp; /* open queue file */ + char *queue_name; /* mail queue id */ + char *queue_id; /* mail queue id */ + long offset; /* data offset */ + char *sender; /* taken from envelope */ + char *recipient; /* taken from resolver */ + char *user; /* recipient lookup handle */ + char *delivered; /* for loop detection */ + char *relay; /* relay host */ + long arrival_time; /* arrival time */ +} DELIVER_ATTR; + +extern void deliver_attr_init(DELIVER_ATTR *); +extern void deliver_attr_dump(DELIVER_ATTR *); + +#define FEATURE_NODELIVERED (1<<0) /* no delivered-to */ + + /* + * Rather than schlepping around dozens of arguments, here is one that has + * all. Well, almost. The user attributes are just a bit too sensitive, so + * they are passed around separately. + */ +typedef struct LOCAL_STATE { + int level; /* nesting level, for logging */ + DELIVER_ATTR msg_attr; /* message attributes */ + DELIVER_REQUEST *request; /* as from queue manager */ +} LOCAL_STATE; + + /* + * Bundle up some often-user attributes. + */ +#define BOUNCE_ATTR(attr) attr.queue_id, attr.recipient, attr.relay, \ + attr.arrival_time +#define SENT_ATTR(attr) attr.queue_id, attr.recipient, attr.relay, \ + attr.arrival_time +#define COPY_ATTR(attr) attr.sender, attr.delivered, attr.fp + +#define MSG_LOG_STATE(m, p) \ + msg_info("%s[%d]: recip %s deliver %s", m, \ + p.level, \ + p.msg_attr.recipient ? p.msg_attr.recipient : "", \ + p.msg_attr.delivered ? p.msg_attr.delivered : "") + + /* + * "inner" nodes of the delivery graph. + */ +extern int deliver_recipient(LOCAL_STATE, USER_ATTR); + + /* + * "leaf" nodes of the delivery graph. + */ +extern int deliver_mailbox(LOCAL_STATE, USER_ATTR, int *); +extern int deliver_file(LOCAL_STATE, USER_ATTR, char *); +extern int deliver_maildir(LOCAL_STATE, USER_ATTR); +extern int deliver_unknown(LOCAL_STATE); + + /* + * Mailbox lock protocol. + */ +extern int virtual_mbox_lock_mask; + +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/*--*/ |
