20120426
- Bugfix (introduced Postfix 2.9): postconf flagged parameters
- defined in master.cf as "unused" when they were used only
- in main.cf. Problem reported by Michael Tokarev. Files:
- postconf/postconf_user.c, postconf/test4b.ref, postconf
- Makefile.in.
+ Bugfix (introduced Postfix 2.9): the postconf command flagged
+ parameters defined in master.cf as "unused" when they were
+ used only in main.cf. Problem reported by Michael Tokarev.
+ Files: postconf/postconf_user.c, postconf/test4b.ref,
+ postconf Makefile.in.
+
+20120513
+
+ Cleanup: report both the first and last line number when a
+ malformed main.cf entry spans multiple lines, instead of
+ reporting the last line number only. File: util/dict.c,
+ util/line_number.[hc].
+
+20120516
+
+ Workaround: apparently, FreeBSD 8.3 kqueue notifications
+ sometimes break when a dnsblog(8) process loses an accept()
+ race on a shared socket, resulting in repeated "connect to
+ private/dnsblog service: Connection refused" warnings. This
+ condition is unique to dnsblog(8). The postscreen(8) daemon
+ closes a postscreen-to-dnsblog connection as soon as it
+ receives a dnsblog(8) reply, resulting in hundreds or
+ thousands of connection requests per second. All other
+ multi-server daemons such as anvil(8) or proxymap(8) have
+ connection lifetimes ranging from 5s to 1000s depending on
+ server load. The workaround is for dnsblog to use the
+ single_server driver instead of the multi_server driver.
+ This one-line code change eliminates the accept() race
+ without any Postfix performance impact. Problem reported
+ by Sahil Tandon. File: dnsblog/dnsblog.c.
+
+ Logging: postscreen now logs a warning when a dnsblog(8)
+ request takes longer than the hard-coded time limit of 10s.
+ File: postscreen/postscreen_dnsbl.c.
+
+20120517
+
+ Workaround: to avoid crashes when the OpenSSL library is
+ updated without "postfix reload", the Postfix TLS session
+ cache ID now includes the OpenSSL library version number.
+ Note: this problem cannot be fixed in tlsmgr(8). Code by
+ Victor Duchovni. Files: tls/tls_server.c, tls_client.c.
+
+20120520
+
+ Bugfix (introduced Postfix 2.4): the event_drain() function
+ was comparing bitmasks incorrectly causing the program to
+ always wait for the full time limit. This error affected
+ the unused postkick command, but only after s/fifo/unix/
+ in master.cf. File: util/events.c.
+
+ Cleanup: laptop users have always been able to avoid
+ unnecessary disk spin-up by doing s/fifo/unix/ in master.cf
+ (this is currently not supported on Solaris systems).
+ However, to make this work reliably, the "postqueue -f"
+ command must wait until its requests have reached the pickup
+ and qmgr servers before closing the UNIX-domain request
+ sockets. Files: postqueue/postqueue.c, postqueue/Makefile.in.
appear on the permanent access list.
NOTE: To share a postscreen(8) cache between multiple postscreen(8)
- instances, use "postscreen_cache_map = proxy:btree:$data_directory/
- postscreen_cache", and disable cache cleanup
+ instances under the same master(8) daemon, use "postscreen_cache_map =
+ proxy:btree:$data_directory/postscreen_cache", and disable cache cleanup
(postscreen_cache_cleanup_interval = 0) in all postscreen(8) instances
except one that is responsible for cache cleanup.
postscreen(8) cache sharing requires Postfix 2.9 or later; earlier proxymap
(8) implementations don't support cache cleanup.
- For an alternative postscreen(8) cache sharing approach see the
+ For an alternative postscreen(8) cache sharing approach, see the
memcache_table(5) manpage.
When the SMTP client address appears on the temporary whitelist, postscreen(8)
O\bOt\bth\bhe\ber\br m\bme\bea\bas\bsu\bur\bre\bes\bs t\bto\bo o\bof\bff\bf-\b-l\blo\boa\bad\bd z\bzo\bom\bmb\bbi\bie\bes\bs
-OpenBSD spamd implements a daemon that handles all connections from "new"
-clients. Only well-behaved mail clients are allowed to talk to the mail server.
-Other clients are tarpitted, and will never get a chance to affect mail server
-performance.
-
-At some point in the future, Postfix may come with a simple front-end daemon
-that does basic greylisting and pipelining detection to keep zombies and other
-ratware away from Postfix itself. This would use the "pass" service type which
-has been available in stable Postfix releases since Postfix 2.5.
+The postscreen(8) daemon, introduced with Postfix 2.8, provides additional
+protection against mail server overload. One postscreen(8) process handles all
+connections from "new" SMTP clients, and allows only well-behaved clients to
+talk to a Postfix SMTP server process. By keeping spambots away, postscreen(8)
+leaves more SMTP server processes available for legitimate clients, and delays
+the onset of server overload conditions.
C\bCr\bre\bed\bdi\bit\bts\bs
Don't forget Apple's code donation for fetching mail from
IMAP server.
+ Make "rename" the default when postmapping a DB file
+ (later: use copy+rename for postmap -i, postmap -d).
+
+ Make the "trigger" service endpoint type configurable. On
+ non-Solaris systems, switching from fifo to unix can avoid
+ disk spin-up due to mtime changes (Postfix on Solaris
+ emulates UNIX-domain sockets by sending a file handle through
+ a FIFO).
+
+ Service-name parameters aren't documented in daemon manpages.
+
When faking up the DSN ORCPT, don't send bare usernames
from local command-line submission.
temporary whitelist is not used for SMTP client addresses
that appear on the <i>permanent</i> access list. </p>
-<blockquote> <p> NOTE: To share a <a href="postscreen.8.html">postscreen(8)</a> cache between
-multiple <a href="postscreen.8.html">postscreen(8)</a> instances, use "<tt><a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> =
-<a href="proxymap.8.html">proxy</a>:btree:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache</tt>", and disable
-cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0) in all
-<a href="postscreen.8.html">postscreen(8)</a> instances except one that is responsible for cache
-cleanup. </p> <p> <a href="postscreen.8.html">postscreen(8)</a> cache sharing requires Postfix 2.9
-or later; earlier <a href="proxymap.8.html">proxymap(8)</a> implementations don't support cache
-cleanup. </p> <p> For an alternative <a href="postscreen.8.html">postscreen(8)</a> cache sharing
-approach see the <a href="memcache_table.5.html">memcache_table(5)</a> manpage. </p> </blockquote>
+<blockquote>
+
+ <p> NOTE: To share a <a href="postscreen.8.html">postscreen(8)</a> cache between multiple
+ <a href="postscreen.8.html">postscreen(8)</a> instances under the same <a href="master.8.html">master(8)</a> daemon, use
+ "<tt><a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> =
+ <a href="proxymap.8.html">proxy</a>:btree:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache</tt>", and disable
+ cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0) in all
+ <a href="postscreen.8.html">postscreen(8)</a> instances except one that is responsible for cache
+ cleanup. </p>
+
+ <p> <a href="postscreen.8.html">postscreen(8)</a> cache sharing requires Postfix 2.9 or later;
+ earlier <a href="proxymap.8.html">proxymap(8)</a> implementations don't support cache cleanup.
+ </p>
+
+ <p> For an alternative <a href="postscreen.8.html">postscreen(8)</a> cache sharing approach,
+ see the <a href="memcache_table.5.html">memcache_table(5)</a> manpage. </p>
+
+</blockquote>
<p> When the SMTP client address appears on the temporary
whitelist, <a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port
<h2><a name="other"> Other measures to off-load zombies </a> </h2>
-<p> OpenBSD <a href="http://www.openbsd.org/spamd/">spamd</a>
-implements a daemon that handles all connections from "new" clients.
-Only well-behaved mail clients are allowed to talk to the mail
-server. Other clients are tarpitted, and will never get a chance
-to affect mail server performance. </p>
-
-<p> At some point in the future, Postfix may come with a simple
-front-end daemon that does basic greylisting and pipelining detection
-to keep zombies and other ratware away from Postfix itself. This
-would use the "pass" service type which has been available in
-stable Postfix releases since Postfix 2.5. </p>
+<p> The <a href="postscreen.8.html">postscreen(8)</a> daemon, introduced with Postfix 2.8, provides
+additional protection against mail server overload. One <a href="postscreen.8.html">postscreen(8)</a>
+process handles all connections from "new" SMTP clients, and allows
+only well-behaved clients to talk to a Postfix SMTP server process.
+By keeping spambots away, <a href="postscreen.8.html">postscreen(8)</a> leaves more SMTP server
+processes available for legitimate clients, and delays the onset
+of server overload conditions. </p>
<h2><a name="credits"> Credits </a></h2>
temporary whitelist is not used for SMTP client addresses
that appear on the <i>permanent</i> access list. </p>
-<blockquote> <p> NOTE: To share a postscreen(8) cache between
-multiple postscreen(8) instances, use "<tt>postscreen_cache_map =
-proxy:btree:$data_directory/postscreen_cache</tt>", and disable
-cache cleanup (postscreen_cache_cleanup_interval = 0) in all
-postscreen(8) instances except one that is responsible for cache
-cleanup. </p> <p> postscreen(8) cache sharing requires Postfix 2.9
-or later; earlier proxymap(8) implementations don't support cache
-cleanup. </p> <p> For an alternative postscreen(8) cache sharing
-approach see the memcache_table(5) manpage. </p> </blockquote>
+<blockquote>
+
+ <p> NOTE: To share a postscreen(8) cache between multiple
+ postscreen(8) instances under the same master(8) daemon, use
+ "<tt>postscreen_cache_map =
+ proxy:btree:$data_directory/postscreen_cache</tt>", and disable
+ cache cleanup (postscreen_cache_cleanup_interval = 0) in all
+ postscreen(8) instances except one that is responsible for cache
+ cleanup. </p>
+
+ <p> postscreen(8) cache sharing requires Postfix 2.9 or later;
+ earlier proxymap(8) implementations don't support cache cleanup.
+ </p>
+
+ <p> For an alternative postscreen(8) cache sharing approach,
+ see the memcache_table(5) manpage. </p>
+
+</blockquote>
<p> When the SMTP client address appears on the temporary
whitelist, postscreen(8) logs this with the client address and port
<h2><a name="other"> Other measures to off-load zombies </a> </h2>
-<p> OpenBSD <a href="http://www.openbsd.org/spamd/">spamd</a>
-implements a daemon that handles all connections from "new" clients.
-Only well-behaved mail clients are allowed to talk to the mail
-server. Other clients are tarpitted, and will never get a chance
-to affect mail server performance. </p>
-
-<p> At some point in the future, Postfix may come with a simple
-front-end daemon that does basic greylisting and pipelining detection
-to keep zombies and other ratware away from Postfix itself. This
-would use the "pass" service type which has been available in
-stable Postfix releases since Postfix 2.5. </p>
+<p> The postscreen(8) daemon, introduced with Postfix 2.8, provides
+additional protection against mail server overload. One postscreen(8)
+process handles all connections from "new" SMTP clients, and allows
+only well-behaved clients to talk to a Postfix SMTP server process.
+By keeping spambots away, postscreen(8) leaves more SMTP server
+processes available for legitimate clients, and delays the onset
+of server overload conditions. </p>
<h2><a name="credits"> Credits </a></h2>
query = vstring_alloc(100);
why = vstring_alloc(100);
result = vstring_alloc(100);
+ var_use_limit = 0;
}
MAIL_VERSION_STAMP_DECLARE;
*/
MAIL_VERSION_STAMP_ALLOCATE;
- multi_server_main(argc, argv, dnsblog_service,
- MAIL_SERVER_TIME_TABLE, time_table,
- MAIL_SERVER_POST_INIT, post_jail_init,
- MAIL_SERVER_UNLIMITED,
- 0);
+ single_server_main(argc, argv, dnsblog_service,
+ MAIL_SERVER_TIME_TABLE, time_table,
+ MAIL_SERVER_POST_INIT, post_jail_init,
+ MAIL_SERVER_UNLIMITED,
+ 0);
}
/* service.
/* DIAGNOSTICS
/* The result is 0 in case of success, -1 in case of failure.
+/* FILES
+/* $queue_directory/public/pickup, server endpoint
+/* $queue_directory/public/qmgr, server endpoint
+/* SEE ALSO
+/* mail_trigger(3), see note about event_drain() usage
/* LICENSE
/* .ad
/* .fi
/* ssize_t length;
/* DESCRIPTION
/* mail_trigger() wakes up the specified mail subsystem, by
-/* sending it the specified request.
+/* sending it the specified request. In the case of non-FIFO
+/* server endpoints, a short-running program should invoke
+/* event_drain() to ensure proper request delivery.
/*
/* Arguments:
/* .IP class
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20120426"
+#define MAIL_RELEASE_DATE "20120520"
#define MAIL_VERSION_NUMBER "2.10"
#ifdef SNAPSHOT
echo 'bar = aaa' >> main.cf
echo smtpd1 unix - n n - 0 smtpd >> master.cf
echo ' -o foo=xxx -o bar=yyy -o baz=zzz' >> master.cf
- echo '#smtpd1 unix - n n - 0 smtpd' >> master.cf
+ echo '#smtpd2 unix - n n - 0 smtpd' >> master.cf
./$(PROG) -nc . >test4b.tmp 2>&1
diff test4b.ref test4b.tmp
rm -f main.cf master.cf test4b.tmp
*/
if (local_scope && dict_get(local_scope->all_params, mac_name)) {
/* $name in master.cf references name=value in master.cf. */
- if (PC_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0)
+ if (PC_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
PC_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
PC_PARAM_FLAG_USER, PC_PARAM_NO_DATA,
convert_user_parameter);
+ if (msg_verbose)
+ msg_info("$%s in %s:%s validates %s=value in %s:%s",
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space,
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space);
+ }
} else if (mail_conf_lookup(mac_name) != 0) {
/* $name in main/master.cf references name=value in main.cf. */
- if (PC_PARAM_TABLE_LOCATE(param_table, mac_name) == 0)
+ if (PC_PARAM_TABLE_LOCATE(param_table, mac_name) == 0) {
PC_PARAM_TABLE_ENTER(param_table, mac_name, PC_PARAM_FLAG_USER,
PC_PARAM_NO_DATA, convert_user_parameter);
+ if (msg_verbose) {
+ if (local_scope)
+ msg_info("$%s in %s:%s validates %s=value in %s",
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space,
+ mac_name, MAIN_CONF_FILE);
+ else
+ msg_info("$%s in %s validates %s=value in %s",
+ mac_name, MAIN_CONF_FILE,
+ mac_name, MAIN_CONF_FILE);
+ }
+ }
}
if (local_scope == 0) {
for (local_scope = master_table; local_scope->argv; local_scope++) {
if (local_scope->all_params != 0
&& dict_get(local_scope->all_params, mac_name) != 0
/* $name in main.cf references name=value in master.cf. */
- && PC_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0)
+ && PC_PARAM_TABLE_LOCATE(local_scope->valid_names, mac_name) == 0) {
PC_PARAM_TABLE_ENTER(local_scope->valid_names, mac_name,
PC_PARAM_FLAG_USER, PC_PARAM_NO_DATA,
convert_user_parameter);
+ if (msg_verbose)
+ msg_info("$%s in %s validates %s=value in %s:%s",
+ mac_name, MAIN_CONF_FILE,
+ mac_name, MASTER_CONF_FILE,
+ local_scope->name_space);
+ }
}
}
return (0);
}
}
+ /*
+ * Scan the "-o parameter=value" instances in each master.cf name space.
+ */
+ for (masterp = master_table; masterp->argv != 0; masterp++)
+ if (masterp->all_params != 0)
+ scan_user_parameter_namespace(masterp->name_space, masterp);
+
/*
* Scan parameter values that are left at their defaults in the global
* name space. Some defaults contain the $name of an obsolete parameter
* Scan the explicit name=value entries in the global name space.
*/
scan_user_parameter_namespace(CONFIG_DICT, (PC_MASTER_ENT *) 0);
-
- /*
- * Scan the "-o parameter=value" instances in each master.cf name space.
- */
- for (masterp = master_table; masterp->argv != 0; masterp++)
- if (masterp->all_params != 0)
- scan_user_parameter_namespace(masterp->name_space, masterp);
}
postqueue.o: ../../include/attr.h
postqueue.o: ../../include/clean_env.h
postqueue.o: ../../include/connect.h
+postqueue.o: ../../include/events.h
postqueue.o: ../../include/flush_clnt.h
postqueue.o: ../../include/iostuff.h
postqueue.o: ../../include/mail_conf.h
#include <connect.h>
#include <valid_hostname.h>
#include <warn_stat.h>
+#include <events.h>
/* Global library. */
if (mail_flush_maildrop() < 0)
msg_fatal_status(EX_UNAVAILABLE,
"Cannot flush mail queue - mail system is down");
+ event_drain(2);
}
/* flush_site - flush mail for site */
score->pending_lookups -= 1;
if (score->pending_lookups == 0)
PSC_CALL_BACK_NOTIFY(score, PSC_NULL_EVENT);
+ } else if (event == EVENT_TIME) {
+ msg_warn("dnsblog reply timeout %ds for %s",
+ DNSBLOG_TIMEOUT, (char *) vstream_context(stream));
}
/* Here, score may be a null pointer. */
vstream_fclose(stream);
continue;
}
stream = vstream_fdopen(fd, O_RDWR);
+ vstream_control(stream,
+ VSTREAM_CTL_CONTEXT, ht[0]->key,
+ VSTREAM_CTL_END);
attr_print(stream, ATTR_FLAG_NONE,
ATTR_TYPE_STR, MAIL_ATTR_RBL_DOMAIN, ht[0]->key,
ATTR_TYPE_STR, MAIL_ATTR_ACT_CLIENT_ADDR, client_addr,
msg_info("%s: TLS cipher list \"%s\"", props->namaddr, cipher_list);
vstring_sprintf_append(myserverid, "&c=%s", cipher_list);
+ /*
+ * Finally, salt the session key with the OpenSSL library version,
+ * (run-time, rather than compile-time, just in case that matters).
+ */
+ vstring_sprintf_append(myserverid, "&l=%ld", (long) SSLeay());
+
/*
* Allocate a new TLScontext for the new connection and get an SSL
* structure. Add the location of TLScontext to the SSL to later retrieve
#define GEN_CACHE_ID(buf, id, len, service) \
do { \
- buf = vstring_alloc(2 * (len) + 1 + strlen(service) + 3); \
+ buf = vstring_alloc(2 * (len + strlen(service))); \
hex_encode(buf, (char *) (id), (len)); \
vstring_sprintf_append(buf, "&s=%s", (service)); \
+ vstring_sprintf_append(buf, "&l=%ld", (long) SSLeay()); \
} while (0)
unix_pass_fd_fix.c dict_cache.c valid_utf_8.c dict_thash.c \
ip_match.c nbbio.c stream_pass_connect.c base32_code.c dict_test.c \
dict_fail.c msg_rate_delay.c dict_surrogate.c warn_stat.c \
- dict_sockmap.c
+ dict_sockmap.c line_number.c
OBJS = alldig.o allprint.o argv.o argv_split.o attr_clnt.o attr_print0.o \
attr_print64.o attr_print_plain.o attr_scan0.o attr_scan64.o \
attr_scan_plain.o auto_clnt.o base64_code.o basename.o binhash.o \
unix_pass_fd_fix.o dict_cache.o valid_utf_8.o dict_thash.o \
ip_match.o nbbio.o stream_pass_connect.o base32_code.o dict_test.o \
dict_fail.o msg_rate_delay.o dict_surrogate.o warn_stat.o \
- dict_sockmap.o
+ dict_sockmap.o line_number.o
HDRS = argv.h attr.h attr_clnt.h auto_clnt.h base64_code.h binhash.h \
chroot_uid.h cidr_match.h clean_env.h connect.h ctable.h dict.h \
dict_cdb.h dict_cidr.h dict_db.h dict_dbm.h dict_env.h dict_ht.h \
username.h valid_hostname.h vbuf.h vbuf_print.h vstream.h vstring.h \
vstring_vstream.h watchdog.h format_tv.h load_file.h killme_after.h \
edit_file.h dict_cache.h dict_thash.h ip_match.h nbbio.h base32_code.h \
- dict_fail.h warn_stat.h dict_sockmap.h
+ dict_fail.h warn_stat.h dict_sockmap.h line_number.h
TESTSRC = fifo_open.c fifo_rdwr_bug.c fifo_rdonly_bug.c select_bug.c \
stream_test.c dup2_pass_on_exec.c
DEFS = -I. -D$(SYSTYPE)
dict.o: dict_ht.h
dict.o: htable.h
dict.o: iostuff.h
+dict.o: line_number.h
dict.o: mac_expand.h
dict.o: mac_parse.h
dict.o: msg.h
killme_after.o: killme_after.c
killme_after.o: killme_after.h
killme_after.o: sys_defs.h
+line_number.o: line_number.c
+line_number.o: line_number.h
+line_number.o: sys_defs.h
+line_number.o: vbuf.h
+line_number.o: vstring.h
line_wrap.o: line_wrap.c
line_wrap.o: line_wrap.h
line_wrap.o: sys_defs.h
#include "dict.h"
#include "dict_ht.h"
#include "warn_stat.h"
+#include "line_number.h"
static HTABLE *dict_table;
VSTRING *buf;
char *member;
char *val;
+ int old_lineno;
int lineno;
const char *err;
struct stat st;
*/
DICT_FIND_FOR_UPDATE(dict, dict_name);
buf = vstring_alloc(100);
- lineno = 0;
+ old_lineno = lineno = 0;
if (fstat(vstream_fileno(fp), &st) < 0)
msg_fatal("fstat %s: %m", VSTREAM_PATH(fp));
- while (readlline(buf, fp, &lineno)) {
+ for ( /* void */ ; readlline(buf, fp, &lineno); old_lineno = lineno) {
if ((err = split_nameval(STR(buf), &member, &val)) != 0)
- msg_fatal("%s, line %d: %s: \"%s\"",
- VSTREAM_PATH(fp), lineno, err, STR(buf));
+ msg_fatal("%s, line %s: %s: \"%s\"",
+ VSTREAM_PATH(fp),
+ format_line_number((VSTRING *) 0,
+ old_lineno + 1, lineno),
+ err, STR(buf));
if (msg_verbose > 1)
msg_info("%s: %s = %s", myname, member, val);
if (dict->update(dict, member, val) != 0)
#define EVENT_MASK_SET(fd, mask) FD_SET((fd), (mask))
#define EVENT_MASK_ISSET(fd, mask) FD_ISSET((fd), (mask))
#define EVENT_MASK_CLR(fd, mask) FD_CLR((fd), (mask))
+#define EVENT_MASK_CMP(m1, m2) memcmp((m1), (m2), EVENT_MASK_BYTE_COUNT(m1))
#else
/*
(EVENT_MASK_FD_BYTE((fd), (mask)) & EVENT_MASK_FD_BIT(fd))
#define EVENT_MASK_CLR(fd, mask) \
(EVENT_MASK_FD_BYTE((fd), (mask)) &= ~EVENT_MASK_FD_BIT(fd))
+#define EVENT_MASK_CMP(m1, m2) \
+ memcmp((m1)->data, (m2)->data, EVENT_MASK_BYTE_COUNT(m1))
#endif
/*
max_time = event_present + time_limit;
while (event_present < max_time
&& (event_timer_head.pred != &event_timer_head
- || memcmp(&zero_mask, &event_xmask,
- EVENT_MASK_BYTE_COUNT(&zero_mask)) != 0)) {
+ || EVENT_MASK_CMP(&zero_mask, &event_xmask) != 0)) {
event_loop(1);
#if (EVENTS_STYLE != EVENTS_STYLE_SELECT)
if (EVENT_MASK_BYTE_COUNT(&zero_mask)
--- /dev/null
+/*++
+/* NAME
+/* line_number 3
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/*
+/* char *format_line_number(result, first, last)
+/* VSTRING *buffer;
+/* ssize_t first;
+/* ssize_t lastl
+/* DESCRIPTION
+/* format_line_number() formats a line number or number range.
+/* The output is <first-number>-<last-number> when the numbers
+/* differ, <first-number> when the numbers are identical.
+/* .IP result
+/* Result buffer, or null-pointer. In the latter case the
+/* result is stored in a static buffer that is overwritten
+/* with subsequent calls. The function result value is a
+/* pointer into the result buffer.
+/* .IP first
+/* First line number.
+/* .IP last
+/* Last line number.
+/* 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 <vstring.h>
+#include <line_number.h>
+
+/* format_line_number - pretty-print line number or number range */
+
+char *format_line_number(VSTRING *result, ssize_t first, ssize_t last)
+{
+ static VSTRING *buf;
+
+ /*
+ * Your buffer or mine?
+ */
+ if (result == 0) {
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ result = buf;
+ }
+
+ /*
+ * Print a range only when the numbers differ.
+ */
+ vstring_sprintf(result, first == last ? "%ld" : "%ld-%ld",
+ (long) first, (long) last);
+
+ return (vstring_str(result));
+}
--- /dev/null
+#ifndef _LINE_NUMBER_H_INCLUDED_
+#define _LINE_NUMBER_H_INCLUDED_
+
+/*++
+/* NAME
+/* line_number 3h
+/* SUMMARY
+/* line number utilities
+/* SYNOPSIS
+/* #include <line_number.h>
+/* DESCRIPTION
+/* .nf
+
+ /*
+ * Utility library.
+ */
+#include <vstring.h>
+
+ /*
+ * External interface.
+ */
+extern char *format_line_number(VSTRING *, ssize_t, ssize_t);
+
+/* 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