Upstream qmail 1.01 djb/1.01
authorMark Wooding <mdw@distorted.org.uk>
Wed, 18 Mar 1998 23:33:14 +0000 (23:33 +0000)
committerMark Wooding <mdw@distorted.org.uk>
Mon, 13 Feb 2006 23:47:50 +0000 (23:47 +0000)
401 files changed:
BLURB [new file with mode: 0644]
BLURB2 [new file with mode: 0644]
BLURB3 [new file with mode: 0644]
BLURB4 [new file with mode: 0644]
CHANGES [new file with mode: 0644]
FAQ [new file with mode: 0644]
FILES [new file with mode: 0644]
INSTALL [new file with mode: 0644]
INSTALL.alias [new file with mode: 0644]
INSTALL.boot [new file with mode: 0644]
INSTALL.ctl [new file with mode: 0644]
INSTALL.ids [new file with mode: 0644]
INSTALL.mbox [new file with mode: 0644]
INSTALL.qsmhook [new file with mode: 0644]
INTERNALS [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
RFCHCSC [new file with mode: 0644]
RFCLOOPS [new file with mode: 0644]
RFCMXPS [new file with mode: 0644]
RFCNETSTR [new file with mode: 0644]
RFCNRUDT [new file with mode: 0644]
RFCQMTP [new file with mode: 0644]
RFCQSBMF [new file with mode: 0644]
RFCVERP [new file with mode: 0644]
SECURITY [new file with mode: 0644]
SYSDEPS [new file with mode: 0644]
TARGETS [new file with mode: 0644]
THANKS [new file with mode: 0644]
THOUGHTS [new file with mode: 0644]
TODO [new file with mode: 0644]
UPGRADE [new file with mode: 0644]
VERSION [new file with mode: 0644]
addresses.5 [new file with mode: 0644]
alloc.3 [new file with mode: 0644]
alloc.c [new file with mode: 0644]
alloc.h [new file with mode: 0644]
alloc_re.c [new file with mode: 0644]
auto-gid.c [new file with mode: 0644]
auto-int.c [new file with mode: 0644]
auto-int8.c [new file with mode: 0644]
auto-str.c [new file with mode: 0644]
auto-uid.c [new file with mode: 0644]
auto_break.h [new file with mode: 0644]
auto_patrn.h [new file with mode: 0644]
auto_qmail.h [new file with mode: 0644]
auto_spawn.h [new file with mode: 0644]
auto_split.h [new file with mode: 0644]
auto_uids.h [new file with mode: 0644]
auto_usera.h [new file with mode: 0644]
byte.h [new file with mode: 0644]
byte_chr.c [new file with mode: 0644]
byte_copy.c [new file with mode: 0644]
byte_cr.c [new file with mode: 0644]
byte_diff.c [new file with mode: 0644]
byte_rchr.c [new file with mode: 0644]
byte_zero.c [new file with mode: 0644]
case.3 [new file with mode: 0644]
case.h [new file with mode: 0644]
case_diffb.c [new file with mode: 0644]
case_diffs.c [new file with mode: 0644]
case_lowerb.c [new file with mode: 0644]
case_lowers.c [new file with mode: 0644]
case_starts.c [new file with mode: 0644]
cdb.3 [new file with mode: 0644]
cdb.h [new file with mode: 0644]
cdb_hash.c [new file with mode: 0644]
cdb_seek.c [new file with mode: 0644]
cdb_unpack.c [new file with mode: 0644]
cdbmake.h [new file with mode: 0644]
cdbmake_add.c [new file with mode: 0644]
cdbmake_hash.c [new file with mode: 0644]
cdbmake_pack.c [new file with mode: 0644]
cdbmss.c [new file with mode: 0644]
cdbmss.h [new file with mode: 0644]
chkshsgr.c [new file with mode: 0644]
chkspawn.c [new file with mode: 0644]
coe.3 [new file with mode: 0644]
coe.c [new file with mode: 0644]
coe.h [new file with mode: 0644]
condredirect.1 [new file with mode: 0644]
condredirect.c [new file with mode: 0644]
conf-break [new file with mode: 0644]
conf-cc [new file with mode: 0644]
conf-groups [new file with mode: 0644]
conf-ld [new file with mode: 0644]
conf-patrn [new file with mode: 0644]
conf-qmail [new file with mode: 0644]
conf-spawn [new file with mode: 0644]
conf-split [new file with mode: 0644]
conf-users [new file with mode: 0644]
constmap.c [new file with mode: 0644]
constmap.h [new file with mode: 0644]
control.c [new file with mode: 0644]
control.h [new file with mode: 0644]
date822fmt.c [new file with mode: 0644]
date822fmt.h [new file with mode: 0644]
datemail.sh [new file with mode: 0644]
datetime.3 [new file with mode: 0644]
datetime.c [new file with mode: 0644]
datetime.h [new file with mode: 0644]
datetime_un.c [new file with mode: 0644]
direntry.3 [new file with mode: 0644]
direntry.h1 [new file with mode: 0644]
direntry.h2 [new file with mode: 0644]
dns.c [new file with mode: 0644]
dns.h [new file with mode: 0644]
dnscname.c [new file with mode: 0644]
dnsdoe.c [new file with mode: 0644]
dnsdoe.h [new file with mode: 0644]
dnsfq.c [new file with mode: 0644]
dnsip.c [new file with mode: 0644]
dnsmxip.c [new file with mode: 0644]
dnsptr.c [new file with mode: 0644]
dot-qmail.9 [new file with mode: 0644]
elq.sh [new file with mode: 0644]
env.3 [new file with mode: 0644]
env.c [new file with mode: 0644]
env.h [new file with mode: 0644]
envelopes.5 [new file with mode: 0644]
envread.c [new file with mode: 0644]
error.3 [new file with mode: 0644]
error.c [new file with mode: 0644]
error.h [new file with mode: 0644]
error_str.3 [new file with mode: 0644]
error_str.c [new file with mode: 0644]
error_temp.3 [new file with mode: 0644]
error_temp.c [new file with mode: 0644]
exit.h [new file with mode: 0644]
extra.h [new file with mode: 0644]
fd.h [new file with mode: 0644]
fd_copy.3 [new file with mode: 0644]
fd_copy.c [new file with mode: 0644]
fd_move.3 [new file with mode: 0644]
fd_move.c [new file with mode: 0644]
fifo.c [new file with mode: 0644]
fifo.h [new file with mode: 0644]
fifo_make.3 [new file with mode: 0644]
find-systype.sh [new file with mode: 0644]
fmt.h [new file with mode: 0644]
fmt_str.c [new file with mode: 0644]
fmt_strn.c [new file with mode: 0644]
fmt_uint.c [new file with mode: 0644]
fmt_uint0.c [new file with mode: 0644]
fmt_ulong.c [new file with mode: 0644]
fmtqfn.c [new file with mode: 0644]
fmtqfn.h [new file with mode: 0644]
forgeries.7 [new file with mode: 0644]
fork.h1 [new file with mode: 0644]
fork.h2 [new file with mode: 0644]
forward.1 [new file with mode: 0644]
forward.c [new file with mode: 0644]
gen_alloc.h [new file with mode: 0644]
gen_allocdefs.h [new file with mode: 0644]
getln.3 [new file with mode: 0644]
getln.c [new file with mode: 0644]
getln.h [new file with mode: 0644]
getln2.3 [new file with mode: 0644]
getln2.c [new file with mode: 0644]
gfrom.c [new file with mode: 0644]
gfrom.h [new file with mode: 0644]
headerbody.c [new file with mode: 0644]
headerbody.h [new file with mode: 0644]
hfield.c [new file with mode: 0644]
hfield.h [new file with mode: 0644]
hostname.c [new file with mode: 0644]
install.c [new file with mode: 0644]
instcheck.c [new file with mode: 0644]
ip.c [new file with mode: 0644]
ip.h [new file with mode: 0644]
ipalloc.c [new file with mode: 0644]
ipalloc.h [new file with mode: 0644]
ipme.c [new file with mode: 0644]
ipme.h [new file with mode: 0644]
ipmeprint.c [new file with mode: 0644]
lock.h [new file with mode: 0644]
lock_ex.c [new file with mode: 0644]
lock_exnb.c [new file with mode: 0644]
lock_un.c [new file with mode: 0644]
maildir.5 [new file with mode: 0644]
maildir.c [new file with mode: 0644]
maildir.h [new file with mode: 0644]
maildir2mbox.1 [new file with mode: 0644]
maildir2mbox.c [new file with mode: 0644]
maildirmake.1 [new file with mode: 0644]
maildirmake.c [new file with mode: 0644]
maildirwatch.1 [new file with mode: 0644]
maildirwatch.c [new file with mode: 0644]
mailsubj.1 [new file with mode: 0644]
mailsubj.sh [new file with mode: 0644]
make-compile.sh [new file with mode: 0644]
make-load.sh [new file with mode: 0644]
make-makelib.sh [new file with mode: 0644]
mbox.5 [new file with mode: 0644]
myctime.c [new file with mode: 0644]
myctime.h [new file with mode: 0644]
ndelay.c [new file with mode: 0644]
ndelay.h [new file with mode: 0644]
ndelay_off.c [new file with mode: 0644]
newfield.c [new file with mode: 0644]
newfield.h [new file with mode: 0644]
now.3 [new file with mode: 0644]
now.c [new file with mode: 0644]
now.h [new file with mode: 0644]
open.h [new file with mode: 0644]
open_append.c [new file with mode: 0644]
open_excl.c [new file with mode: 0644]
open_read.c [new file with mode: 0644]
open_trunc.c [new file with mode: 0644]
open_write.c [new file with mode: 0644]
pinq.sh [new file with mode: 0644]
predate.c [new file with mode: 0644]
preline.1 [new file with mode: 0644]
preline.c [new file with mode: 0644]
prioq.c [new file with mode: 0644]
prioq.h [new file with mode: 0644]
prot.c [new file with mode: 0644]
prot.h [new file with mode: 0644]
qail.sh [new file with mode: 0644]
qbiff.1 [new file with mode: 0644]
qbiff.c [new file with mode: 0644]
qlist.1 [new file with mode: 0644]
qlist.c [new file with mode: 0644]
qlist2.sh [new file with mode: 0644]
qlx.h [new file with mode: 0644]
qmail-clean.8 [new file with mode: 0644]
qmail-clean.c [new file with mode: 0644]
qmail-command.8 [new file with mode: 0644]
qmail-config.sh [new file with mode: 0644]
qmail-control.9 [new file with mode: 0644]
qmail-getpw.9 [new file with mode: 0644]
qmail-getpw.c [new file with mode: 0644]
qmail-header.5 [new file with mode: 0644]
qmail-hier.c [new file with mode: 0644]
qmail-inject.8 [new file with mode: 0644]
qmail-inject.c [new file with mode: 0644]
qmail-limits.9 [new file with mode: 0644]
qmail-local.8 [new file with mode: 0644]
qmail-local.c [new file with mode: 0644]
qmail-log.5 [new file with mode: 0644]
qmail-lspawn.8 [new file with mode: 0644]
qmail-lspawn.c [new file with mode: 0644]
qmail-newu.9 [new file with mode: 0644]
qmail-newu.c [new file with mode: 0644]
qmail-pop3d.8 [new file with mode: 0644]
qmail-pop3d.c [new file with mode: 0644]
qmail-popup.8 [new file with mode: 0644]
qmail-popup.c [new file with mode: 0644]
qmail-pw2u.9 [new file with mode: 0644]
qmail-pw2u.c [new file with mode: 0644]
qmail-qmtpd.8 [new file with mode: 0644]
qmail-qmtpd.c [new file with mode: 0644]
qmail-qread.8 [new file with mode: 0644]
qmail-qread.c [new file with mode: 0644]
qmail-qstat.8 [new file with mode: 0644]
qmail-qstat.sh [new file with mode: 0644]
qmail-queue.8 [new file with mode: 0644]
qmail-queue.c [new file with mode: 0644]
qmail-remote.8 [new file with mode: 0644]
qmail-remote.c [new file with mode: 0644]
qmail-rspawn.8 [new file with mode: 0644]
qmail-rspawn.c [new file with mode: 0644]
qmail-send.9 [new file with mode: 0644]
qmail-send.c [new file with mode: 0644]
qmail-showctl.8 [new file with mode: 0644]
qmail-showctl.c [new file with mode: 0644]
qmail-smtpd.8 [new file with mode: 0644]
qmail-smtpd.c [new file with mode: 0644]
qmail-start.9 [new file with mode: 0644]
qmail-start.c [new file with mode: 0644]
qmail-tcpto.8 [new file with mode: 0644]
qmail-tcpto.c [new file with mode: 0644]
qmail-upgrade.9 [new file with mode: 0644]
qmail-upq.sh [new file with mode: 0644]
qmail-users.9 [new file with mode: 0644]
qmail.7 [new file with mode: 0644]
qmail.c [new file with mode: 0644]
qmail.h [new file with mode: 0644]
qreceipt.1 [new file with mode: 0644]
qreceipt.c [new file with mode: 0644]
qsmhook.c [new file with mode: 0644]
qsutil.c [new file with mode: 0644]
qsutil.h [new file with mode: 0644]
quote.c [new file with mode: 0644]
quote.h [new file with mode: 0644]
readsubdir.c [new file with mode: 0644]
readsubdir.h [new file with mode: 0644]
readwrite.h [new file with mode: 0644]
received.c [new file with mode: 0644]
received.h [new file with mode: 0644]
remoteinfo.c [new file with mode: 0644]
remoteinfo.h [new file with mode: 0644]
scan.h [new file with mode: 0644]
scan_8long.c [new file with mode: 0644]
scan_nbblong.c [new file with mode: 0644]
scan_ulong.c [new file with mode: 0644]
seek.h [new file with mode: 0644]
seek_cur.c [new file with mode: 0644]
seek_end.c [new file with mode: 0644]
seek_set.c [new file with mode: 0644]
seek_trunc.c [new file with mode: 0644]
select.h1 [new file with mode: 0644]
select.h2 [new file with mode: 0644]
sendmail.c [new file with mode: 0644]
sgetopt.3 [new file with mode: 0644]
sgetopt.c [new file with mode: 0644]
sgetopt.h [new file with mode: 0644]
sig.h [new file with mode: 0644]
sig_alarm.c [new file with mode: 0644]
sig_block.c [new file with mode: 0644]
sig_bug.c [new file with mode: 0644]
sig_catch.c [new file with mode: 0644]
sig_child.c [new file with mode: 0644]
sig_hup.c [new file with mode: 0644]
sig_misc.c [new file with mode: 0644]
sig_pause.c [new file with mode: 0644]
sig_pipe.c [new file with mode: 0644]
sig_term.c [new file with mode: 0644]
slurpclose.c [new file with mode: 0644]
slurpclose.h [new file with mode: 0644]
spawn.c [new file with mode: 0644]
splogger.8 [new file with mode: 0644]
splogger.c [new file with mode: 0644]
str.h [new file with mode: 0644]
str_chr.c [new file with mode: 0644]
str_cpy.c [new file with mode: 0644]
str_diff.c [new file with mode: 0644]
str_diffn.c [new file with mode: 0644]
str_len.c [new file with mode: 0644]
str_rchr.c [new file with mode: 0644]
str_start.c [new file with mode: 0644]
stralloc.3 [new file with mode: 0644]
stralloc.h [new file with mode: 0644]
stralloc_arts.c [new file with mode: 0644]
stralloc_cat.c [new file with mode: 0644]
stralloc_catb.c [new file with mode: 0644]
stralloc_cats.c [new file with mode: 0644]
stralloc_copy.c [new file with mode: 0644]
stralloc_eady.c [new file with mode: 0644]
stralloc_opyb.c [new file with mode: 0644]
stralloc_opys.c [new file with mode: 0644]
stralloc_pend.c [new file with mode: 0644]
strerr.h [new file with mode: 0644]
strerr_die.c [new file with mode: 0644]
strerr_sys.c [new file with mode: 0644]
subfd.h [new file with mode: 0644]
subfderr.c [new file with mode: 0644]
subfdin.c [new file with mode: 0644]
subfdins.c [new file with mode: 0644]
subfdout.c [new file with mode: 0644]
subfdouts.c [new file with mode: 0644]
subgetopt.3 [new file with mode: 0644]
subgetopt.c [new file with mode: 0644]
subgetopt.h [new file with mode: 0644]
substdi.c [new file with mode: 0644]
substdio.c [new file with mode: 0644]
substdio.h [new file with mode: 0644]
substdio_copy.c [new file with mode: 0644]
substdo.c [new file with mode: 0644]
tcp-env.1 [new file with mode: 0644]
tcp-env.c [new file with mode: 0644]
tcp-environ.5 [new file with mode: 0644]
tcpto.c [new file with mode: 0644]
tcpto.h [new file with mode: 0644]
tcpto_clean.c [new file with mode: 0644]
timeoutconn.c [new file with mode: 0644]
timeoutconn.h [new file with mode: 0644]
timeoutread.c [new file with mode: 0644]
timeoutread.h [new file with mode: 0644]
timeoutwrite.c [new file with mode: 0644]
timeoutwrite.h [new file with mode: 0644]
token822.c [new file with mode: 0644]
token822.h [new file with mode: 0644]
trigger.c [new file with mode: 0644]
trigger.h [new file with mode: 0644]
triggerpull.c [new file with mode: 0644]
triggerpull.h [new file with mode: 0644]
trycpp.c [new file with mode: 0644]
trydrent.c [new file with mode: 0644]
tryflock.c [new file with mode: 0644]
trylsock.c [new file with mode: 0644]
trymkffo.c [new file with mode: 0644]
trynpbg1.c [new file with mode: 0644]
tryrsolv.c [new file with mode: 0644]
trysalen.c [new file with mode: 0644]
trysgact.c [new file with mode: 0644]
trysgprm.c [new file with mode: 0644]
tryshsgr.c [new file with mode: 0644]
trysysel.c [new file with mode: 0644]
trysyslog.c [new file with mode: 0644]
tryulong32.c [new file with mode: 0644]
tryvfork.c [new file with mode: 0644]
trywaitp.c [new file with mode: 0644]
uint32.h1 [new file with mode: 0644]
uint32.h2 [new file with mode: 0644]
wait.3 [new file with mode: 0644]
wait.h [new file with mode: 0644]
wait_nohang.c [new file with mode: 0644]
wait_pid.c [new file with mode: 0644]
warn-auto.sh [new file with mode: 0644]
warn-shsgr [new file with mode: 0644]

diff --git a/BLURB b/BLURB
new file mode 100644 (file)
index 0000000..7bcfb3f
--- /dev/null
+++ b/BLURB
@@ -0,0 +1,43 @@
+qmail is a secure, reliable, efficient, simple message transfer agent.
+It is meant as a replacement for the entire sendmail-binmail system on
+typical Internet-connected UNIX hosts.
+
+Secure: Security isn't just a goal, but an absolute requirement. Mail
+delivery is critical for users; it cannot be turned off, so it must be
+completely secure. (This is why I started writing qmail: I was sick of
+the security holes in sendmail and other MTAs.)
+
+Reliable: qmail's straight-paper-path philosophy guarantees that a
+message, once accepted into the system, will never be lost. qmail also
+supports maildir, a new, super-reliable user mailbox format. Maildirs,
+unlike mbox files and mh folders, won't be corrupted if the system
+crashes during delivery. Even better, not only can a user safely read
+his mail over NFS, but any number of NFS clients can deliver mail to him
+at the same time.
+
+Efficient: On a Pentium under BSD/OS, qmail can easily sustain 200000
+local messages per day---that's separate messages injected and delivered
+to mailboxes in a real test! Although remote deliveries are inherently
+limited by the slowness of DNS and SMTP, qmail overlaps 20 simultaneous
+deliveries by default, so it zooms quickly through mailing lists. (This
+is why I finished qmail: I had to get a big mailing list set up.)
+
+Simple: qmail is vastly smaller than any other Internet MTA. Some
+reasons why: (1) Other MTAs have separate forwarding, aliasing, and
+mailing list mechanisms. qmail has one simple forwarding mechanism that
+lets users handle their own mailing lists. (2) Other MTAs offer a
+spectrum of delivery modes, from fast+unsafe to slow+queued. qmail-send
+is instantly triggered by new items in the queue, so the qmail system
+has just one delivery mode: fast+queued. (3) Other MTAs include, in
+effect, a specialized version of inetd that watches the load average.
+qmail's design inherently limits the machine load, so qmail-smtpd can
+safely run from your system's inetd.
+
+Replacement for sendmail: qmail supports host and user masquerading,
+full host hiding, virtual domains, null clients, list-owner rewriting,
+relay control, double-bounce recording, arbitrary RFC 822 address lists,
+cross-host mailing list loop detection, per-recipient checkpointing,
+downed host backoffs, independent message retry schedules, etc. In
+short, it's up to speed on modern MTA features. qmail also includes a
+drop-in ``sendmail'' wrapper so that it will be used transparently by
+your current UAs.
diff --git a/BLURB2 b/BLURB2
new file mode 100644 (file)
index 0000000..a63f21c
--- /dev/null
+++ b/BLURB2
@@ -0,0 +1,26 @@
+Mailing list management is one of qmail's strengths. Notable features:
+
+* qmail lets each user handle his own mailing lists. The delivery
+instructions for user-whatever go into ~user/.qmail-whatever.
+
+* qmail makes it really easy to set up mailing list owners. If the user
+touches ~user/.qmail-whatever-owner, all bounces will come back to him.
+
+* qmail supports VERPs, which permit completely reliable automated
+bounce handling for mailing lists of any size.
+
+* SPEED---qmail blasts through mailing lists an order of magnitude
+faster than sendmail. For example, one message was successfully
+delivered to 150 hosts around the world in just 70 seconds, with qmail's
+out-of-the-box configuration.
+
+* qmail automatically prevents mailing list loops, even across hosts.
+
+* qlist, included in the qmail package, deals with subscription requests
+safely and automatically.
+
+* qmail allows inconceivably gigantic mailing lists. No random limits.
+
+* qmail handles aliasing and forwarding with the same simple mechanism.
+For example, Postmaster is controlled by ~alias/.qmail-postmaster. This
+means that cross-host loop detection also applies to aliases.
diff --git a/BLURB3 b/BLURB3
new file mode 100644 (file)
index 0000000..db3b699
--- /dev/null
+++ b/BLURB3
@@ -0,0 +1,90 @@
+Here are some of qmail's features. 
+
+Setup:
+*  automatic adaptation to your UNIX variant---no configuration needed
+*  AIX, BSD/OS, FreeBSD, HP/UX, Irix, Linux, OSF/1, SunOS, Solaris, and more
+*  automatic per-host configuration (qmail-config)
+*  quick installation---no big list of decisions to make
+
+Security:
+*  clear separation between addresses, files, and programs
+*  minimization of setuid code (qmail-queue)
+*  minimization of root code (qmail-start, qmail-lspawn)
+*  five-way trust partitioning---security in depth
+*  optional logging of one-way hashes, entire contents, etc. (QUEUE_EXTRA)
+
+Message construction (qmail-inject):
+*  RFC 822, RFC 1123
+*  full support for address groups
+*  automatic conversion of old-style headers to RFC 822 format
+*  header line length limited only by memory
+*  host masquerading (control/defaulthost)
+*  user masquerading (MAILUSER, MAILHOST)
+*  sendmail hook for compatibility with current user agents
+
+SMTP service (qmail-smtpd):
+*  RFC 821, RFC 1123, RFC 1651, RFC 1652, RFC 1854
+*  8-bit clean
+*  931/1413/ident/TAP callback (tcp-env)
+*  relay control---stop unauthorized relaying by outsiders (control/rcpthosts)
+*  no interference between relay control and forwarding
+*  tcpd hook---reject SMTP connections from known abusers
+*  automatic recognition of local IP addresses
+*  per-buffer timeouts
+*  hop counting
+
+Queue management (qmail-send):
+*  instant handling of messages added to queue
+*  parallelism limit (control/concurrencyremote, control/concurrencylocal)
+*  split queue directory---no slowdown when queue gets big
+*  quadratic retry schedule---old messages tried less often
+*  independent message retry schedules
+*  automatic safe queueing---no loss of mail if system crashes
+*  automatic per-recipient checkpointing
+*  automatic queue cleanups (qmail-clean)
+*  queue viewing (qmail-qread)
+*  detailed delivery statistics (qmailanalog, available separately)
+
+Bounces (qmail-send):
+*  QSBMF bounce messages---both machine-readable and human-readable
+*  HCMSSC support---language-independent RFC 1893 error codes
+*  double bounces sent to postmaster
+
+Routing by domain (qmail-send):
+*  any number of names for local host (control/locals)
+*  any number of virtual domains (control/virtualdomains)
+*  domain wildcards (control/virtualdomains)
+*  configurable percent hack support (control/percenthack)
+*  UUCP hook
+
+SMTP delivery (qmail-remote):
+*  RFC 821, RFC 974, RFC 1123
+*  8-bit clean
+*  automatic downed host backoffs
+*  artificial routing---smarthost, localnet, mailertable (control/smtproutes)
+*  per-buffer timeouts
+*  passive SMTP queue---perfect for SLIP/PPP (serialmail, available separately)
+
+Forwarding and mailing lists (qmail-local):
+*  address wildcards (.qmail-default, .qmail-foo-default, etc.)
+*  sendmail/smail /etc/aliases compatibility (qmsmac, available separately)
+*  mailing list owners---automatically divert bounces and vacation messages
+*  VERPs---automatic recipient identification for mailing list bounces
+*  Delivered-To---automatic loop prevention, even across hosts
+*  automatic subscription management (qlist)
+
+Local delivery (qmail-local):
+*  user-controlled address hierarchy---fred controls fred-anything
+*  mbox delivery
+*  reliable NFS delivery (maildir)
+*  user-controlled program delivery: procmail etc. (qmail-command)
+*  optional new-mail notification (qbiff)
+*  optional NRUDT return receipts (qreceipt)
+*  conditional filtering (condredirect)
+
+POP3 service (qmail-popup, qmail-pop3d):
+*  RFC 1939
+*  UIDL support
+*  TOP support
+*  APOP hook
+*  modular password checking (checkpassword, available separately)
diff --git a/BLURB4 b/BLURB4
new file mode 100644 (file)
index 0000000..35d2cd3
--- /dev/null
+++ b/BLURB4
@@ -0,0 +1,44 @@
+qmail's modular, lightweight design and sensible queue management make
+it the fastest available message transfer agent. Here's how it stacks up
+against the competition in five different speed measurements.
+
+* Scheduling: I sent a message to 8192 ``trash'' recipients on my home
+machine. All the deliveries were done in a mere 78 seconds---a rate of
+over 9 MILLION deliveries a day! Compare this to the speed advertised
+for Zmailer's scheduling: 1.1 million deliveries a day on a
+SparcStation-10/50. (My home machine is a 16MB Pentium-100 under BSD/OS,
+with the default qmail configuration. qmail's logs were piped through
+accustamp and written to disk as usual.)
+
+* Local mailing lists: When qmail is delivering a message to a mailbox,
+it physically writes the message to disk before it announces success---
+that way, mail doesn't get lost if the power goes out. I tried sending a
+message to 1024 local mailboxes on the same disk on my home machine; all
+the deliveries were done in 25.5 seconds. That's more than 3.4 MILLION
+deliveries a day! Sending 1024 copies to a _single_ mailbox was just as
+fast. Compare these figures to Zmailer's advertised rate for throwing
+recipients away without even delivering the message---only 0.48 million
+per day on the SparcStation.
+
+* Mailing lists with remote recipients: qmail uses the same delivery
+strategy that makes LSOFT's LSMTP so fast for outgoing mailing lists---
+you choose how many parallel SMTP connections you want to run, and qmail
+runs exactly that many. Of course, performance varies depending on how
+far away your recipients are. The advantage of qmail over other packages
+is its smallness: for example, one Linux user is running 60 simultaneous
+connections, without swapping, on a machine with just 16MB of memory!
+
+* Separate local messages: What LSOFT doesn't tell you about LSMTP is
+how many _separate_ messages it can handle in a day. Does it get bogged
+down as the queue fills up? On my home machine, I disabled qmail's
+deliveries and then sent 5000 separate messages to one recipient. The
+messages were all safely written to the queue disk in 23 minutes, with
+no slowdown as the queue filled up. After I reenabled deliveries, all
+the messages were delivered to the recipient's mailbox in under 12
+minutes. End-to-end rate: more than 200000 individual messages a day!
+
+* Overall performance: What really matters is how well qmail performs
+with your mail load. Red Hat Software found one day that their mail hub,
+a 48MB Pentium running sendmail 8.7, was running out of steam at 70000
+messages a day. They shifted the load to qmail---on a _smaller_ machine,
+a 16MB 486/66---and now they're doing fine.
diff --git a/CHANGES b/CHANGES
new file mode 100644 (file)
index 0000000..6abdaf3
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,1204 @@
+19970415 version: qmail 1.01.
+19970414 doc: tightened up qmail-upgrade.7.
+19970414 code: rewrote rewrite().
+19970414 code: implemented recipientmap. suggested by RDM.
+19970414 doc: auto-configured qmail home directory in qmail-control.5,
+         qmail-newu.8, qmail-pw2u.8, qmail-start.8, qmail-users.5.
+19970414 port: Solaris needs socket libs for gethostname. impact: can't
+         compile under Solaris. fix: use socket.lib for qmail-local.
+19970412 code: introduced stralloc_starts.
+19970412 code: introduced str_equal.
+19970412 code: introduced str_start.
+19970412 code: introduced byte_equal.
+19970412 code: made an optional aliasempty arg for qmail-start.
+19970412 code: made an aliasempty arg for qmail-lspawn.
+19970412 code: changed ALIAS_EMPTY to an arg for qmail-local.
+19970412 port: UnixWare returns >0 for SIOCGIFCONF. impact: ipme fails
+         under UnixWare. fix: check for >=0, not =0. tnx JD.
+19970412 port: DGUX does not have ranlib. impact: can't compile under
+         DGUX. fix: added dgux line to make-makelib. tnx HWM.
+19970412 code: changed maildir library to skip any filename beginning
+         with dot. tnx SP.
+19970412 doc: added FAQ entry about aliases with dots.
+19970412 doc: clarified in qmail-inject.8 that default envelope sender
+         is the same as _default_ From address.
+19970411 code: renamed qmail-makectl as qmail-config.
+19970411 code: renamed qmail-alias as qmail-local.
+19970411 code: switched from signal library to sig library.
+19970411 code: switched from qqtalk library to qmail library.
+19970411 code: switched from getline library to getln library.
+19970411 code: massive library cleanups.
+19970411 code: revamped autoconfiguration system.
+19970411 code: revamped configuration interface.
+19970411 code: eliminated qmail-home.
+19970411 code: eliminated tokenize.
+19970220 qmail 1.00.
+19970219 change: various documentation tweaks.
+19970218 change: updated THOUGHTS.
+19970218 change: talked about SPAWN_NUMD in FAQ. tnx EC.
+19970210 change: noted in maildir.5 that readers should skip any name
+         starting with a dot. tnx SP.
+19970209 change: added note to splogger.8 about reliability. tnx BT.
+19970209 change: added section to FAQ on slow sendmail switch. tnx BT.
+19970206 change: added section to FAQ about dtcm. tnx PJG.
+19970206 change: tweaked maildir.5.
+19970201 change: added MH spost note to FAQ. tnx TU.
+19970131 change: reorganized FAQ.
+19970131 change: added web references to FAQ.
+19970124 change: tweaked qmail-upgrade man page.
+19970120 qmail 0.96, gamma.
+19970120 change: removed various try* in auto-configuration.
+19970120 bug: qmail-inject fails to quote argument addresses. impact:
+         addresses containing special characters could be misinterpreted
+        or rejected. tnx C2F. fix: use quote2().
+19970120 portability problem: ESIX puts syslog() and openlog() into
+         -lgen. impact: can't compile under ESIX. fix: put -lgen into
+        LIBS for unix_sv. tnx RN.
+19961221 qmail 0.95, gamma.
+19961218 change: added various try* to TARGETS. tnx SA.
+19961216 change: clarified in qmail-send.8 that virtualdomains does not
+         apply to domains listed in locals.
+19961216 change: slurpclose() now closes fd on out-of-memory. makes it
+         more widely applicable.
+19961215 change: replaced elm instructions in INSTALL.mbox with an
+         explanation of what source change to make. tnx AB.
+19961212 portability problem: under NEWS-OS, time_t needs sys/types.h.
+         impact: couldn't compile under NEWS-OS. fix: include
+        sys/types.h in predate.c. tnx TU.
+19961211 change: used timeoutread, timeoutwrite in remoteinfo(). tnx
+         REB.
+19961210 portability problem: apparently some SGIs produce a systype of
+         irix64. impact: couldn't compile on those systems. fix: handle
+        irix64 in make-cmds. tnx M3S.
+19961208 change: added note to maildir2mbox.1 about mbox locking.
+19961208 qmail 0.94, gamma.
+19961207 change: added QMAILDEFAULTDOMAIN, QMAILDEFAULTHOST,
+         QMAILIDHOST, QMAILPLUSDOMAIN. tnx BTW.
+19961206 cleanup: readsubdir() protects itself against name overflow,
+         rather than depending on caller.
+19961204 change: changed FAQ 7.3 to prohibit fixup relaying.
+19961203 change: added note to FAQ about possibly having to put a space
+         before "$SENDER" for uux. tnx FW.
+19961202 change: added FAQ entry on QUEUE_EXTRA.
+19961202 change: added FAQ entry on backups. tnx DP.
+19961202 change: added note to INSTALL.mbox about qpopper. tnx VV.
+19961201 change: replaced logger with splogger in FAQ 5.1. tnx FPL.
+19961201 change: used netmask example for tcpcontrol in FAQ. tnx FPL.
+19961201 change: added note to README about the mailing list. tnx FPL.
+19961201 change: documented rcpthosts wildcards. tnx RN.
+19961201 change: added note to FAQ about making mailx use datemail.
+19961201 change: added datemail. function requested by several people;
+         approach suggested by TG.
+19961201 change: added predate.
+19961129 change: added QUEUE_EXTRA, QUEUE_EXTRALEN.
+19961129 change: qmail-remote bounces messages with partial final lines.
+19961129 change: added atomcheck() to quote crappy atoms.
+19961129 change: revised atomok() to let atoms deal with more crap.
+19961127 change: qmail-send adds note to deferral if flagdying. tnx TG.
+19961127 change: split off maildirbounce, maildir2qmtp, and maildir2smtp
+         into a separate serialmail package.
+19961126 change: eliminated beta success reports from README.
+19961124 change: forced res_query() return value to fit inside incoming
+         buffer size. allegedly there are buggy versions of res_query()
+        that don't guarantee this.
+19961122 qmail 0.93, gamma.
+19961122 change: allowed empty arg list in forward.
+19961121 change: qmail-smtpd now uses unknown (like qmail-qmtpd) rather
+         than dying if environment variables are not set.
+19961121 cleanup: reorganized helo handling in qmail-smtpd.c.
+19961121 cleanup: eliminated newfield_rec.
+19961121 cleanup: introduced DATE822FMT. used it in received.c.
+19961121 cleanup: introduced received.c. used it in qmail-qmtpd.c,
+         qmail-smtpd.c.
+19961121 change: qmail-smtpd now generates a new timestamp for each
+         message. tnx PJG.
+19961121 cleanup: used stralloc in newfield.
+19961121 cleanup: eliminated newfield_cc.
+19961121 change: eliminated hfield_mort().
+19961119 change: added 2-minute damper on tcpto.
+19961118 change: wrote defaults for readfile controls in showctl.
+19961117 change: control_readfile() now allows comments and blank lines.
+         tnx LW.
+19961117 change: qmail-start sets logger gid to GID_NOFILES.
+19961117 bug: ipme_init() uses a fixed-length buffer for SIOCGIFCONF.
+         impact: qmail-smtpd and qmail-remote will die if there are too
+        many local IP addresses. tnx MD. fix: ipme_init() now
+        dynamically allocates space, up to 200000 bytes, as long as
+        SIOCGIFCONF keeps failing. note that this is a very widespread
+        bug; it's in amd, exim, mrouted, named, nntpd, rarpd, sendmail,
+        tcpdump, timed, xntpd, and probably dozens more programs.
+19961117 portability problem: on BSD 4.4 and various other systems,
+         SIOCGIFCONF will truncate long lists and return success.
+        impact: on those systems, qmail-smtpd and qmail-remote will
+        miss some local IP addresses if there are too many. fix:
+        ipme_init() now checks whether there is enough space left in
+        the buffer for another ifreq, plus 64 bytes JIC. yuck.
+19961117 change: ipmeprint now flushes only at end.
+19961117 cleanup: introduced subfdinsmall. used it in qmail-clean.c,
+         qmail-qmtpd.c.
+19961117 cleanup: introduced subfdoutsmall. used it in hostname.c,
+         printbreak.c, printnumd.c, printsplit.c, qmail-alias.c,
+        qmail-clean.c, qmail-getpw.c, qmail-qmtpd.c, maildir2smtp.c,
+        maildir2qmtp.c.
+19961117 change: moved subfderr buf up to 256 characters.
+19961117 change: added maildirbounce. tnx MD, TG.
+19961116 change: maildir2smtp and maildir2qmtp now print filenames for
+         permanent bounces. tnx MD.
+19961116 change: in SECURITY, ``eleven most recent sendmail security
+         holes, five'' -> ``twelve most recent sendmail security holes,
+        six.''
+19961116 change: rewrote qmail-showctl more professionally.
+19961115 change: added several tests to find-systype.sh. this will
+         affect many systypes.
+19961115 change: qmail-alias now treats most exit codes as soft errors.
+19961115 change: revamped exit codes everywhere for 0, 100, 111.
+19961114 change: added splogger.
+19961114 portability problem: Sun's cc recognizes sqrt() as a builtin,
+         even if math.h is not included and sqrt is defined statically.
+        yuck. impact: when qmail is compiled with Sun's cc, next-retry
+        times are all screwed up. tnx PJG. fix: my sqrt() is now called
+        squareroot().
+19961114 change: dns_ip() now recognizes [1.2.3.4]. tnx DS.
+19961112 change: enabled x option in sendmail. tnx DS.
+19961111 change: added SIGHUP handling to qmail-send. suggested by many
+         people.
+19961111 bug: control routines returned incorrect codes for some
+         out-of-memory conditions. impact: none; conditions cannot
+        happen with sane control files. fix: return -1.
+19961111 change: added SIGALRM handling to qmail-send. suggested by many
+         people.
+19961111 change: eliminated flagnobreak (-b/-B) from qmail-pw2u.
+19961111 change: qmail-getpw now allows hyphens inside usernames.
+19961111 change: added users/append to qmail-pw2u. tnx G2A.
+19961111 change: added badmailfrom to qmail-smtpd. requested by several
+         people.
+19961110 change: replaced elm instructions in INSTALL.mbox with a simple
+         note to set incomingfolders in elm.rc. tnx AB.
+19961110 change: replaced ``owner hack'' with ``variable envelope
+         return paths'' throughout the documentation. tnx DS.
+19961110 change: qmail-setup installs man pages as well as cat pages.
+19961110 change: renamed qmail-newuser as qmail-newu. tnx G2A.
+19961110 change: renamed qmail-pw2user as qmail-pw2u. tnx G2A.
+19961105 change: set path in INSTALL.boot. tnx TJH.
+19961105 change: noted in qmail-smtpd.8 that addresses without @ are
+         always allowed through.
+19961105 change: indicated at various spots in FAQ that rcpthosts has to
+         be updated. suggested by various people.
+19961105 change: indicated at various spots in FAQ that qmail has to be
+         restarted. suggested by various people.
+19961029 change: fixed typo in maildir2qmtp.1. tnx BG.
+19961026 qmail 0.92, gamma.
+19961026 bug: qmail-getpw did not 0-terminate usernames. tnx CF. impact:
+         qmail-getpw would crash on some systems, deferring local
+        deliveries. fix: 0-terminate.
+19961025 cleanup: renamed auto-hassgprm.h to hassgprm.h.
+19961025 cleanup: renamed auto-hassgact.h to hassgact.h.
+19961024 change: replaced qmail-alias.0 with dot-qmail.0 in
+         INSTALL.alias. tnx MW.
+19961022 change: switched uids as early as possible in qmail-start.c.
+19961022 change: in SECURITY, ``ten most recent sendmail security
+         holes, five'' -> ``eleven most recent sendmail security holes,
+        five.''
+19961022 change: quote_need() now treats non-ASCII characters the same
+         way as control characters.
+19961022 change: added version and home page to qmail.7.
+19961022 cleanup: introduced slurpclose.c. used it in qmail-alias.c,
+         qmail-lspawn.c.
+19961021 portability problem: AT&T NCR boxes need stdio.h before
+         arpa/nameser.h. impact: dns.c would not compile. fix: include
+        stdio.h. tnx HS.
+19961021 change: added AIX section to INSTALL.ids. tnx SSB.
+19961021 change: added qmail-pw2user.
+19961020 change: added qmail-pw2user.8.
+19961020 change: qmail-alias now dies soft on EACCES/EPERM for .qmail.
+19961020 change: eliminated root comments from INSTALL.qsmhook.
+19961020 change: various improvements in FAQ.
+19961017 change: added QLX_ROOT.
+19961017 change: renamed hosts in FAQ. tnx SLB.
+19961017 change: in dot-qmail.5, documented envnoathost effects. tnx RN.
+19961017 change: revamped addresses.5.
+19961017 change: added stripvdomprepend() for better bounces. tnx PT.
+19961012 portability problem: under HP-UX 9, can't setgroups() to 65537.
+         impact: couldn't compile under HP-UX 9. fix: use 0 instead of
+        65537 in chkshsgr.c. tnx HHO.
+19961008 change: added several qlx codes.
+19961008 cleanup: eliminated qlx from qmail-alias.
+19961008 change: qmail-lspawn runs qmail-getpw as UID_PW.
+19961008 change: added qmail-newuser.
+19961008 change: added cdb support to qmail-lspawn.
+19961008 change: integrated cdb.
+19961007 change: added qmail-users.5.
+19961007 change: eliminated usermap.
+19961007 cleanup: switched execvp to execv in sendmail, qmail-lspawn.
+19961007 change: used qmail-getpw in qmail-lspawn.
+19961007 change: renamed LSPAWN_USERLEN as GETPW_USERLEN.
+19961007 change: added qmail-getpw.
+19961007 change: created users subdirectory of CONF_HOME.
+19961007 change: fixed typo in FAQ. tnx J1B.
+19961006 change: replaced subfdout with a small ss in qmail-alias.
+19961006 change: reduced qmail-alias buffer sizes to 1024.
+19961003 change: added note to maildir2smtp.0 about maildirmake. tnx SM.
+19961003 bug: if ipme_init() returned -1, qmail-remote would continue,
+         blindly assuming that all addresses are local. impact: on
+        systems with too many aliases, all remote deliveries fail. tnx
+        MD. fix: qmail-remote now dies with temp_oserr() on any result
+        other than 1.
+19961003 portability problem: all pre-4.9.4 versions of bind barf,
+         badly, on CNAME queries to lame servers. what a crappy system.
+        even if the resolver doesn't barf, the next name server down
+        the line may barf. impact: qmail can't get mail through to
+        domains that are (1) lame and (2) running old versions of bind.
+        fix: never, ever, do a CNAME query. dns_cname() now does an ANY
+        query instead. this, like sendmail's analogous procedure, is
+        unreliable when a CNAME is mixed with other records.
+19961001 cleanup: switched to libfd in qmail-start.c.
+19960929 cleanup: renamed auto-hasmkffo.h to hasmkffo.h.
+19960928 cleanup: reorganized qmail-start.c.
+19960928 cleanup: used libfd in preline.c, qmail-lspawn.c,
+         qmail-popup.c, qmail-rspawn.c, qmail-start.c, qqtalk.c,
+        qsmhook.c.
+19960928 cleanup: added libfd.
+19960927 change: in SECURITY, ``nine most recent sendmail security
+         holes, four'' -> ``ten most recent sendmail security holes,
+        five.''
+19960926 change: added tcpcontrol notes to FAQ.
+19960926 change: qmail-smtpd now immediately closes connection, with a
+         warning message dedicated to Solaris, if stray newlines show up
+        in the incoming data.
+19960926 change: added INSTALL.boot.
+19960926 portability problem: on systems that can handle IP interface
+         aliases (i.e., on sa_len systems), SIOCGIFADDR returns the
+        primary address for an alias. impact: ipme_init() did not
+        include alias addresses. fix: ipme_init() avoids SIOCGIFADDR on
+        sa_len systems; on these systems, the address we want is
+        already in ifr. tnx DM.
+19960926 change: qmail-alias kills itself if locking takes longer than
+         30 seconds.
+19960926 change: qmail-pop3d no longer moves messages. tnx RS.
+19960924 change: added note to FAQ about descriptors limit. tnx RD.
+19960922 change: open_trunc() now uses 644.
+19960922 change: qmail-setup now does umask(077).
+19960922 change: maildir2mbox now does umask(077).
+19960922 change: moved subfderr buf up to 64 characters.
+19960920 change: in SECURITY, ``eight most recent sendmail security
+         holes, three'' -> ``nine most recent sendmail security holes,
+        four.''
+19960920 portability problem: init run commands are subject to job
+         control signals under more systems than HP-UX. impact: on some
+        systems (e.g., Solaris), qmail daemons would be killed. fix:
+        INSTALL now tells everybody to use csh -cf.
+19960920 change: added queue-run section to FAQ.
+19960920 change: in pine-crashing question in FAQ, added -oem and -oi,
+         so that change will work with the real sendmail too.
+19960919 change: added CNAME section to FAQ. tnx to various people.
+19960919 change: eliminated QQX_EXECHARD and QQT_EXECHARD. this means
+         that all qmail-queue invocation failures are now soft, even
+        things like EPERM.
+19960919 change: replaced ``No such address'' with ``Sorry, no mailbox
+         here by that name.'' tnx G2A.
+19960919 change: qmail-remote now includes host name in no-such-host
+         messages. tnx G2A.
+19960919 change: replaced ``Temporarily unable to canonicalize address''
+         with ``CNAME lookup failed temporarily.''
+19960918 change: improved an error message in qmail-alias.c. tnx TG.
+19960918 change: added SHELL=/bin/sh to Makefile. tnx JL.
+19960916 change: reorganized INSTALL.ids a bit.
+19960916 change: ``from smtpd'' is now ``from network''.
+19960916 change: SMTPD is now DAEMON.
+19960916 change: qmail-start sets logger uid to UID_LOG. tnx JLH.
+19960916 change: added CONF_USERL.
+19960916 change: iaafmt() now puts a dot on in-addr.arpa.
+19960915 change: added UPGRADE. suggested by several people.
+19960915 change: added qsutil error messages to qmail-log.5.
+19960915 change: qsutil error messages are now alerts.
+19960915 portability problem: on some systems, logger appears to use
+         syslog(pri,buf) instead of syslog(pri,"%s",buf). tnx JC.
+        impact: logger could barf or crash if fed messages containing
+        %. an attacker could easily cause a crash, eliminating qmail's
+        logs. fix: % is no longer considered safe for logs.
+19960912 cleanup: split seek.c into seek_*.c.
+19960912 cleanup: replaced seek_to() with seek_set().
+19960912 cleanup: introduced libseek.a.
+19960907 cleanup: split case.c into case_*.c.
+19960907 cleanup: introduced libcase.a.
+19960907 cleanup: split wait.c into wait_*.c.
+19960907 cleanup: introduced libwait.a.
+19960907 cleanup: renamed auto-haswaitp.h to haswaitp.h.
+19960907 cleanup: split open.c into open_*.c.
+19960907 cleanup: introduced libopen.a.
+19960904 change: added generic pointer to qmail-control.5. tnx HW.
+19960904 change: rewrote rcpthosts section in FAQ. tnx HW.
+19960904 change: added organization section to FAQ. tnx HW.
+19960902 qmail 0.91, gamma.
+19960902 change: control_readfile() can now handle partial lines. tnx
+         JDHB.
+19960902 change: eliminated non-fqdn note from FAQ. next version of
+         tcpserver will use DNS directly.
+19960902 change: qlist now uses NEWSENDER, not SENDER.
+19960902 change: qmail-pop3d no longer obtains a lock. tnx RS.
+19960902 change: put }g on all seds in Makefile.
+19960831 change: noted in qmail-control.5 that comments are not allowed
+         in control files. tnx J2B.
+19960829 change: used double union in alloc.c. tnx ME.
+19960829 change: replaced semicolon with colon for smtproutes port.
+19960829 change: in INSTALL, put make man just before make setup.
+19960829 change: changed a few qmail messages into alerts.
+19960829 cleanup: renamed datetime_gmt as datetime_tai.
+19960829 change: added note to UUCP question that some UUCP software
+         doesn't want preline -f. tnx SB.
+19960829 change: added question 2.4 to FAQ on SLIP/PPP.
+19960828 change: replaced owner- with owner-@host-@[] in qmail-inject.
+19690828 change: replaced owner- with owner-@host-@[] in qmail-alias.
+19960828 change: replaced owner- with owner-@host-@[] in injectbounce().
+19960828 change: replaced owner- with owner-@host-@[] in senderadd() for
+         owner hack.
+19960828 change: qmail-inject -n now prints Return-Path.
+19960825 cleanup: revised ending code in token_addrlist(). 
+19960825 change: tokenize now uses linelen 0 for unparse.
+19960825 change: if linelen is 0 in token822_unparse, no length limit.
+19960825 change: added LINELEN macro to qmail-inject for unparse.
+19960825 change: token822_unparse now takes linelen argument. (leaving
+         two spaces on the right before linelen.)
+19960824 cleanup: renamed token as token822.
+19960822 portability problem: under NEWS-OS, /bin/mail and /usr/ucb/mail
+         invoke sendmail with -E and -J options. tnx TU. impact:
+        couldn't send mail with those programs. fix: accept opts,
+        including _optional_ args. ugh.
+19960821 change: sendmail now quits if invoked as newaliases. tnx TU.
+19960821 portability problem: under NEWS-OS, dirent.h needs sys/types.h.
+         tnx TU. this POSIX violation also appears in some versions of
+        FreeBSD. impact: couldn't compile under NEWS-OS. fix: include
+        sys/types.h in direntry.h* and trydrent.c. [sigh]
+19960821 change: added concurrencyremote question to FAQ.
+19960821 change: added chkspawn.
+19960821 change: moved default SPAWN_NUMD up to 120.
+19960818 change: allowed ;port in smtproutes. tnx AL.
+19960818 change: introduced port in qmail-remote.c.
+19960818 change: qmail-queue records qp in Received lines. 2 lines of
+         code. tnx ME.
+19960818 change: in SECURITY, ``seven most recent sendmail security
+         holes'' -> ``eight most recent sendmail security holes.''
+19960818 change: qmail-pop3d now appends an extra blank line to every
+         message, for compatibility with popper. some clients can't
+        deal with the right thing, unfortunately. tnx FPL.
+19960818 change: added qmail-tcpto.
+19960818 change: eliminated cc -posix for NeXTs. tnx SA.
+19960818 change: eliminated loadfifo. tnx SA.
+19960818 change: integrated auto-configured fifo.c code from SA.
+19960817 change: put SYSDEPS into a more reasonable order.
+19960813 change: indicated possibility of duplication when qmail-remote
+         gets a dead connection after DATA. tnx ME.
+19960813 change: documented qmail-inject environment variables.
+19960813 change: supported per-recipient owner hack in qmail-inject.
+19960813 change: supported per-message owner hack in qmail-inject.
+19960813 change: introduced hackedruser into qmail-inject.
+19960813 change: introduced QMAILRUSER, QMAILRHOST.
+19960812 change: added QMAILINJECT option to allow address-comment form.
+19960812 change: made name-address form the default in qmail-inject.
+19960812 change: added QMAILINJECT options f and m to delete From and
+         Message-ID on input. tnx LL.
+19960812 change: added QMAILINJECT environment variable.
+19960812 change: added QMAILHOST, QMAILUSER, QMAILNAME to override
+         MAILHOST, MAILUSER, MAILNAME. tnx MG.
+19960812 change: added qmail-showctl.
+19960812 portability problem: under Solaris 2.4 and possibly other
+         systems, the linker does not give generic alignment to an array
+        of 4096 chars. tnx JP. impact: some subset of the programs
+        would (reliably) die with a bus error; in the Solaris case,
+        maildir2mbox. fix: redefine space in alloc.c to be aligned.
+19960812 change: qmail-remote no longer does CNAME lookups if there's an
+         artificial SMTP route. tnx ME.
+19960812 change: added flagcname arg to addrmangle() in qmail-remote.
+19960812 cleanup: moved host/relayhost processing earlier in
+         qmail-remote.
+19960812 change: qmail-remote stops before DATA if no RCPTs were
+         successful. tnx JLH.
+19960812 change: rewrote rcpthosts explanation in FAQ.
+19960811 change: added qmail-log.5.
+19960811 change: introduced ALIAS_PATERNALISM. configurability requested
+         by several people.
+19960811 change: eliminated go-writability test for qmeox(). the alleged
+         value of paternalism is nonexistent if nobody even notices
+        you're doing it.
+19960811 change: in qbiff, changed no-/-allowed to no-/-at-beginning,
+         no-dots-allowed, must-be-nonempty. tnx MD.
+19960811 change: in mbox.5, discouraged mail readers from looking for
+         From_ lines only after blank lines. too much crap in the world.
+19960811 change: added subject line to qreceipt success notices.
+19960811 change: added subject line to qmail-send bounce messages.
+19960811 change: qmail-alias now expects dash arg. this finally gives
+         lspawn complete control over the local -> ~user/.qmail-ext map.
+19960811 change: qmail-lspawn passes dash arg to qmail-alias.
+19960811 change: reorganized qlist acknowledgment format. again.
+19960811 change: documented EXT, EXT2, EXT3, EXT4. tnx BB.
+19960810 change: qmail-makectl now copies locals to rcpthosts. should be
+         a better default. suggested by TK.
+19960805 portability problem: new makefile generator put in tabs again.
+         sigh. impact: couldn't compile under some systems. fix: same as
+        before. tnx TG.
+19960804 change: added tcpserver instructions to FAQ.
+19960804 change: reorganized FAQ server instructions into a new section.
+19960801 qmail 0.90, gamma.
+19960801 change: qmail-qmtpd now supports rcpthosts, RELAYCLIENT.
+19960731 change: default NUMD is now 29. this prepares for weird systems
+         where getpwnam() needs more than one descriptor (but the
+        descriptor limit is still 64! ... you never know), and for
+        possible future getpwnam() replacements.
+19960731 change: popped subfderr buffer up to 32 characters. made sure
+         that everybody flushed subfderr as necessary.
+19960731 change: maildir2qmtp now prints filenames and responses.
+19960731 change: maildir2smtp now prints filenames it's trying and
+         relevant portion of SMTP responses.
+19960731 change: used succwrite() in maildir2smtp, maildir2qmtp.
+         simplifies code quite a bit.
+19960731 change: qmail-remote's blast() checks sooner for write errors.
+19960731 change: added better -e option to sendmail. tnx TG.
+19960731 change: added maildir2qmtp.
+19960730 cleanup: eliminated die_nomem() in maildir2smtp.c.
+19960730 change: dns_cname now pretends that "foo." is a CNAME for "foo"
+        to give the desired behavior for people who misuse DNS and
+        violate RFC 822. tnx RN.
+19960730 change: dns_cname now tests for empty names and ] on every
+         loop.
+19960730 change: used LSPAWN_BREAK in qmail-send.c for usermap.
+19960730 change: updated header example in qmail-header.5.
+19960730 change: added printbreak. auto-configured BREAK in dot-qmail.5,
+         qmail-lspawn.7, qmail-send.8, qmail-upgrade.7, qlist2.
+19960730 change: added printnumd. auto-configured NUMD in qmail-send.8,
+         qmail-limits.8.
+19960730 change: added printsplit. auto-configured split in qmail-upq.
+19960730 change: added dot-qmail.5.
+19960730 change: qmail-smtpd now treats HELO as including RSET.
+19960730 change: added moreinfo to qlist usage message.
+19960729 change: improved an error message in qmail-alias.
+19960729 cleanup: merged qmeox(), qmeodx().
+19960729 bug: failure to stat .qmail-owner was not an error. impact: if
+         stat failed temporarily (e.g., because of NFS), .qmail-owner
+        would be incorrectly ignored, so outgoing message would have
+        wrong envelope sender. fix: qmail-alias does temp_nfsqmail() if
+        stat() returns a temporary error.
+19960729 change: added RFCOWNER.
+19960729 change: added qmtpd setup question to FAQ.
+19960729 change: added qmail-qmtpd.
+19960728 change: revamped maildir2smtp error messages.
+19960728 change: revamped maildirwatch error messages.
+19960728 change: revamped maildir2mbox error messages.
+19960728 change: used strerr in maildir_scan().
+19960728 change: used strerr in maildir_chdir().
+19960728 change: introduced strerr.
+19960728 bug: the new tcp-env tried to read from an ndelay socket.
+         impact: TCPREMOTEINFO would always be empty. fix: unset ndelay
+        in remoteinfo.c.
+19960728 bug: if maildir2smtp saw a permanent failure after MAIL, it
+         failed to do RSET. impact: all further messages would be
+        rejected at the MAIL stage. fix: maildir2smtp now always does
+        RSET. tnx JW.
+19960728 cleanup: qmail-alias now applies lowercase and dot-to-colon
+         conversion directly to dashext, leaving everything else alone.
+        this works since all .qmail access is factored through dashext.
+19960727 portability problem: under NeXTStep, -posix is almost entirely
+         broken. impact: qmail daemons would dump core under NeXTStep.
+        fix: turn off -posix, except for loading qmail-setup, which
+        needs mkfifo(); NeXT, bless them, didn't put mkfifo() into the
+        C library where it belongs. this requires a new make command,
+        namely loadfifo.
+19960727 change: all characters 33-126 are now considered safe for logs.
+         tnx MD.
+19960727 cleanup: eliminated qp variable from mailforward().
+19960727 cleanup: maildirwatch.c includes headerbody.h.
+19960727 cleanup: eliminated match from maildirwatch.c.
+19960727 cleanup: eliminated code variable from maildir2smtp.c:doit().
+19960727 cleanup: maildir2smtp.c includes scan.h.
+19960727 cleanup: maildir.c includes str.h.
+19960727 cleanup: qmail-popup.c now includes exit.h.
+19960727 cleanup: qmail-pop3d.c now includes exit.h.
+19960727 cleanup: eliminated path from qmail-start.c.
+19960727 cleanup: eliminated birthplusnn from nextretry().
+19960727 cleanup: eliminated r from timeoutconn().
+19960727 cleanup: tcpto.c now includes byte.h.
+19960727 cleanup: spawn.c now declares initialize().
+19960727 cleanup: qmail-lspawn.c now includes str.h, byte.h.
+19960727 cleanup: qmail-inject.c now includes quote.h.
+19960727 change: qmail-check now checks separately for group
+         readability and other readability.
+19960727 bug: maildir2smtp didn't check flagehlo in PIPELINING parsing.
+         impact: a server that said PIPELINING at any point, not just
+        EHLO, would receive pipelined data. fix: check flagehlo.
+19960727 bug: readsubdir was calling pause(). impact: if a subdirectory
+         was removed, qmail-send would hang. fix: use rs->pause().
+19960727 change: used error_str in qmail-qread.
+19960727 change: qmail-qread now looks for local/remote open errors.
+19960727 cleanup: added warn() in qmail-qread.c.
+19960727 change: qmail-qread now exits 111 for temporary errors.
+19960727 change: used error_str in qmail-setup.
+19960727 change: introduced error_str.
+19960727 change: replaced qmail-check with make check in INSTALL.
+19960727 change: added check target to Makefile.
+19960727 change: replaced qmail-setup with make setup in INSTALL.
+19960727 change: indirected fake targets through do- targets.
+19960727 change: added setup target to Makefile.
+19960727 change: qmail-makectl now makes sure that defaultdomain has
+         at least one dot. e.g., enteract.com -> enteract.com, not com.
+19960726 bug: quote() failed to quote commas. impact: addresses
+         containing commas would not have been quoted correctly for
+        Return-Path or for SMTP MAIL FROM. fix: quote commas.
+19960726 change: sendmail now mentions qmail-qread, not qmail-mailq.
+19960726 change: qmail-alias now expects ext arg. this eliminates
+         LSPAWN_BREAK from qmail-alias and gives lspawn almost complete
+        control over the local -> ~user/.qmail-ext transformation. the
+        exception is that qmail-alias always uses ~user/.qmail,
+        ignoring ext, if local is the same as user.
+19960726 change: qmail-lspawn passes ext to qmail-alias.
+19960726 change: alloc() now uses up a 4K space before calling malloc().
+19960726 change: ipalloc allocation base is now 10. 100 was silly.
+19960726 change: stralloc allocation base is now 30.
+19960726 change: injectbounce() now supports the owner hack.
+19960726 change: qmail-smtpd no longer requires HELO. tnx K1J.
+19960726 cleanup: replaced makereceived() with dohelo().
+19960726 change: qmail-smtpd is back to 555 for syntax errors.
+19960725 change: qmail-alias now supports the owner hack. tnx to RN for
+         prodding me to look at this problem.
+19960725 change: senderadd() now supports the owner hack.
+19960725 cleanup: split off senderadd().
+19960725 change: added pine-crashing note to FAQ.
+19960725 change: added procmail config.h note to INSTALL.mbox.
+19960725 change: added elm TMPDIR note to INSTALL.mbox.
+19960725 change: added pine.conf note to INSTALL.mbox.
+19960724 change: added fixup note to FAQ.
+19960724 change: qmail-inject now exits 111 for temporary errors.
+19960724 change: qmail-smtpd now appends RELAYCLIENT to incoming
+         recipient domain names.
+19960724 cleanup: moved relayclient out of qmail-smtpd's addrallowed()
+         into caller.
+19960724 change: added rcpthosts wildcards.
+19960724 change: added clean target to Makefile.
+19960723 change: added virtualdomains exceptions.
+19960722 change: added BLURB4.
+19960722 change: added BLURB3.
+19960722 change: eliminated smarthost and localnet.
+19960722 change: incorporated relaymap, contributed by LW. renamed it
+         as smtproutes.
+19960722 change: qmail-popup now supports APOP. suggested by BG, who
+         distributed similar changes.
+19960722 change: qmail-popup now sends APOP timestamp to checkpassword.
+19960722 cleanup: in qmail-popup, split off doanddie().
+19960722 change: qmail-popup now prints APOP timestamp in banner.
+19960722 change: added hostname argument to qmail-popup.
+19960722 cleanup: in qmail-popup, split out() into out(), outflush().
+19960722 cleanup: in qmail-popup, introduced pop3_greet().
+19960721 portability problem: under Unisys SVR4, hostname is not in the
+         usual path. impact: qmail-makectl fails. fix: added hostname
+        command here, used it in qmail-makectl.
+19960721 portability problem: on some sysctl-based systems, apparently
+         gethostname() doesn't write anything if the output buffer is
+        too small. it should write a truncated name. impact: if anyone
+        has a hostname longer than 64 characters, maildirs could get up
+        to 64 characters of garbage, rather than a truncated hostname.
+        fix: qmail-alias now does *host = 0 before calling gethostname.
+19960721 change: updated FAQ examples from qsmhook to preline.
+19960721 change: added preline.
+19960721 change: qsmhook now uses signal_init, signal_uninit.
+19960721 change: qsmhook now checks specifically for empty args.
+19960721 change: documented mbox.
+19960721 change: added EXT, EXT2, EXT3, EXT4.
+19960721 change: added LAST response to qmail-pop3d, always returning
+         OK 0. tnx RN.
+19960721 change: added qmail home page to README.
+19960721 change: added HELP response to qmail-smtpd. tnx RN.
+19960720 change: expanded, vertically, the qmail-inject error message
+         for unparseable header fields.
+19960720 change: logo is now dolphin. tnx CEJ.
+19960719 qmail 0.76, beta.
+19960719 change: used LSPAWN_BREAK in qmail-alias for deciding how to
+         handle extensions. this should produce better behavior in the
+        (unsupported) case that LSPAWN_BREAK is not a hyphen.
+19960719 bug: qmail-smtpd didn't check for null arg on MAIL, RCPT.
+         impact: qmail-smtpd would deref 0 and crash. fix: qmail-smtpd
+        now gives syntax error on null arg.
+19960719 change: documented UFLINE in qmail-command.8. tnx TG.
+19960718 change: added maildir2smtp.
+19960718 cleanup: introduced maildir.c. used it in maildir2mbox.c,
+         maildirwatch.c.
+19960718 change: added maildirwatch.
+19960718 cleanup: maildir2mbox now sets up pq2 as it is deleting from
+         pq, rather than simultaneously with pq.
+19960718 change: added H_DELIVEREDTO.
+19960718 portability problem: Unisys requires -lsocket -lnsl. impact:
+         couldn't compile under Unisys. fix: added unix_sv section to
+        make-cmds.sh. tnx TVP.
+19960718 change: added unix_sv section in find-systype. tnx TVP.
+19960717 change: qmail-alias now appends newline if .qmail does not end
+         with a newline. tnx MC.
+19960717 change: qmail-alias now defers delivery for a blank line only
+         if it is the first line of the file. handles user behavior
+        described by MC of putting many newlines at end of file.
+19960717 bug: qmail-inject looked for dots in user part, not just host
+         part, when deciding whether to use defaultdomain. impact: the
+        address joe.bloggs@here didn't have defaultdomain added. fix:
+        qmail-inject now stops at the @.
+19960717 change: updated INSTALL.alias to mention qmsmac.
+19960717 change: syntax error code for SMTP is now 501.
+19960717 change: added -e option to sendmail. tnx TG.
+19960716 change: changed ~alias files to .qmail-local, not .qmaillocal.
+        suggested by many people.
+19960716 change: redid qmail-alias/qmail-lspawn interface.
+19960716 change: replaced EXTENSION, USEREXT with LOCAL.
+19960716 change: qmail-queue now removes intd, mess upon error, as long
+        as it doesn't time out. suggested by BB et al.
+19960716 change: added flagmademess, flagmadeintd to qmail-queue.c.
+19960716 cleanup: changed todofd to intdfd in qmail-queue.c.
+19960716 cleanup: added cleanup() to qmail-queue.c.
+19960716 change: added timeout to tcp-env.c, default 30 seconds.
+19960716 change: remoteinfo_get() now uses timeoutconn().
+19960715 change: added procmail config.h note to FAQ.
+19960704 change: qmail-upgrade.7 now warns administrators that ~alias
+         generally doesn't apply to addresses starting with a user name.
+19960703 change: added echo \c note to FAQ. tnx PJG.
+19960702 change: qmail-smtpd now accepts HELO without an argument.
+         tnx K1J, J1B.
+19960627 change: qmail-lspawn.8 now mentions that qmail-lspawn doesn't
+         set up supplementary groups. tnx TG.
+19960625 portability problem: under Linux, read(,,0) doesn't do proper
+         error slippage. impact: timeoutconn() would always report
+        success; if a connection failed, qmail-remote would report a
+        greeting failure and skip all further MX records. tnx ME. fix:
+        timeoutconn() now uses getpeername() to check for success.
+19960625 change: qmail-smtpd now mentions disk full for QQT_WRITE.
+19960625 change: qmail-inject now mentions disk full for QQT_WRITE.
+19960622 change: if RELAYCLIENT is set, qmail-smtpd skips rcpthosts.
+19960609 change: updated INSTALL for current SMTP responses.
+19960607 change: clarified INSTALL.qsmhook examples. tnx S1R.
+19960607 change: added subject parsing to qlist.c. tnx RN.
+19960607 cleanup: used case_diffb in qlist.c.
+19960607 change: added extra log information to INSTALL examples.
+19960606 change: added -Pn to uucp line in FAQ. tnx DWS.
+19960605 portability problem: under Solaris, /usr/bin/groups incorrectly
+         reports your groups in /etc/group, rather than the results of
+        getgroups(). tnx MD, PJG. impact: test #19 in INSTALL fails.
+        fix: added special note to test #19 (sigh) about Solaris.
+19960605 change: improved maildir setup commands in INSTALL.mbox.
+19960605 change: on success, qmail-alias logs forwarding qp. 9 lines
+         extra code.
+19960605 change: qmail-send logs qp for bounce. 6 lines extra code.
+19960605 change: qmail-smtpd includes qp in its response when it accepts
+         a message. 7 lines extra code. requested by MD and others.
+19960605 change: added qqtalk_qp.
+19960605 change: qmail-send now logs uid and qp from todo file. 14 lines
+         extra code.
+19960605 change: qmail-queue now records uid and qp in u and p lines
+         in todo file. 7 lines extra code.
+19960605 change: improved qmail-alias x-bit error messages.
+19960605 change: newline in log is now converted to /, not underscore.
+19960604 change: when it accepts a message, qmail-smtpd includes the
+         local time in its 250 response.
+19960604 change: on success, qmail-alias prints delivery counts,
+         file+forward+program.
+19960603 change: qmail-remote now reports IP address on success. tnx MD.
+19960603 change: qmail-send now logs success and failure reports, not
+         just deferral reports.
+19960603 change: added netbsd section in find-systype, same as bsd.os
+         section. this will affect netbsd-* systypes. tnx MBS.
+19960530 qmail 0.75, beta.
+19960528 change: added qmail.7. tnx MD.
+19960525 change: added qmail-pop3d. tnx RN.
+19960525 change: added qmail-popup. tnx RN.
+19960525 change: added elm filter section to FAQ. tnx GB.
+19960502 portability problem: on many systems, select() on an
+         almost-full pipe incorrectly says writable. tnx ME for running
+        into this and helping track it down. impact: if qmail-send
+        writes a pipeful to qmail-lspawn or qmail-rspawn before they
+        can react (because of high concurrency, high load, or long
+        addresses), it will receive an incorrect -1/EAGAIN, and will
+        conclude that spawn died. sysadmin will have to restart qmail,
+        and messages will be duplicated. fix: in qmail-send.c,
+        busy-loop if write() to spawn returns any error other than
+        EPIPE.
+19960501 bug: qmail-alias treated NAMETOOLONG and NOTDIR as temporary
+         errors. impact: qmail-alias never looked for -default; even if
+        mail was destined to bounce, it would have to time out first.
+        fix: qmail-alias now uses error_temp().
+19960430 bug: qmail-smtpd treated qq crash as permanent error. impact:
+         if somebody actively kills qq, mail will be incorrectly
+        bounced. tnx SS. fix: qmail-smtpd now treats only TOOLONG and
+        EXECHARD as permanent errors.
+19960430 cleanup: eliminated QQT_TTY from qqtalk.h.
+19960428 change: added ``warning: '' before trouble-marking message.
+19960428 change: added percenthack. requested by GB.
+19960428 cleanup: switched to auto-generated Makefile.
+19960428 cleanup: switched to auto-generated .o dependencies.
+19960428 cleanup: eliminated fmt.o, scan.o from Makefile.
+19960428 portability problem: under HP-UX 10, the rc pgrp is sent HUP
+         when rc finishes. tnx BG. impact: the qmail daemons are killed
+        when rc finishes. fix: added special note in INSTALL (sigh) to
+        use csh -cf.
+19960427 cleanup: added PORT_SMTP in qmail-remote.c.
+19960427 cleanup: introduced timeoutwrite.c. used it in qmail-remote.c.
+19960427 cleanup: introduced timeoutread.c. used it in qmail-remote.c.
+19960427 cleanup: introduced timeoutconn.c. used it in qmail-remote.c.
+19960427 change: added timeoutconnect. default: 60 seconds.
+19960427 change: added pop3d instructions to FAQ. tnx RN.
+19960427 change: eliminated env manipulation from qmail-start. tnx BG.
+19960427 change: headerbody now ends header, inserting blank line, if
+         first line of a header field doesn't pass hfield_valid. tnx TG.
+19960427 change: headerbody now prepends MBOX-Line: to any header line
+         starting From_. this lets qmail-inject work with elm's bounce.
+        tnx OR, K1J, et al.
+19960426 change: added moreinfo arg to qlist and qlist2.
+19960426 change: added signal_uncatchchild() to qmail-send.c. tnx BG.
+         now, if sysadmin sets SIGCHLD to SIG_IGN before invoking
+        qmail-send [sigh], qmail-send won't screw up bounce messages.
+19960426 change: dns_cname now checks whether last character is ],
+         rather than whether first character is [, for quick return.
+19960426 cleanup: glue is now global in dns.c.
+19960426 cleanup: qmail-remote no longer does stralloc_0 for host and
+         canonhost.
+19960426 change: dns_mxip no longer rejects [foo].bar.
+19960426 change: dns_mxip no longer requires for bracket that input
+         be 0-terminated.
+19960426 change: qmail-start can now take logger as an argument.
+19960426 change: qmail-start now invokes qmail-send in foreground (as
+         parent of other processes).
+19960426 change: added mailsubj. tnx GAW.
+19960426 portability problem: under some systems, can't lock read-only
+         file. impact: maildir2mbox would always fail on those systems.
+        fix: maildir2mbox now opens a separate lock fd. tnx BG.
+19960426 cleanup: removed unnecessary #!/bin/sh and # AUTO from mctl.sh.
+19960426 change: added qmail-qstat.
+19960426 change: added qmail-qread.8.
+19960426 change: renamed qmail-mailq as qmail-qread.
+19960419 change: qmail-alias now defers delivery rather than skipping
+         blank lines in .qmail.
+19960419 change: in qmail-lspawn.c, lowercased name before getpwnam().
+         really getpwnam() should do this, but oh well.
+19960419 change: added username to qmail-lspawn.c, with LSPAWN_USERLEN
+         in conf-unusual.h. names longer than LSPAWN_USERLEN will skip
+        getpwnam().
+19960419 change: if qlist doesn't see any cmds, it presumes that you
+         meant to subscribe.
+19960419 change: reorganized qlist acknowledgment format.
+19960415 change: reorganized and rewrote FAQ.
+19960415 change: renamed HOWTO as FAQ.
+19960414 change: in qmail-alias, converted extension to lowercase just
+         before qmeopen(), qmeox() calls. thus EXTENSION and USEREXT and
+        RECIPIENT will preserve case passed by qmail-lspawn, while
+        .qmailext lookups will not.
+19960414 change: removed case_lowers(r) from qmail-lspawn.c. tnx JLH.
+19960414 change: moved extension . -> : conversion to just before
+         qmeopen(), qmeox() calls in qmail-alias.c. thus EXTENSION and
+        USEREXT and RECIPIENT will preserve dots.
+19960414 change: qsmhook -x now does case-independent comparison.
+19960413 change: added procmail instructions to HOWTO.
+19960409 bug: qmail-alias does not check for newlines when it generates
+         Return-Path. impact: resulting Return-Path header field will be
+        illegal, if sender address contains newline followed by
+        something other than whitespace. fix: qmail-alias now replaces
+        newline with underscore in rpline.
+19960409 change: added leaf UUCP description to HOWTO. tnx J2K.
+19960409 change: added -B option to sendmail. tnx OR.
+19960409 change: qlist now makes lists unwritable (after renaming from
+         .qtemp to .qmail). tnx MLH.
+19960409 change: added flagdtline to qsmhook.c, based on -l option.
+19960409 change: added PIPELINING declaration to qmail-smtpd. tnx JGM.
+19960409 change: qmail-smtpd now flushes output instantly after DATA,
+         QUIT, HELO, EHLO, NOOP, VRFY, or any 502.
+19960409 change: qmail-smtpd now flushes output upon read() and death.
+19960409 change: qmail-smtpd no longer flushes output in out().
+19960409 change: increased qmail-smtpd outbuf size from 128 to 512.
+19960409 cleanup: in qmail-smtpd, eliminated ssinit() in favor of FDBUF.
+19960409 bug: qmail-alias produced aliasfoo-owner rather than foo-owner
+         as envelope sender for ~alias/.qmailfoo. tnx DS. impact: wrong
+        envelope sender whenever ~alias/.qmailfoo-owner existed. fix:
+        qmail-alias now checks for hyphen at beginning of extension.
+19960409 change: added _ESMTP to end of 220. tnx JLH.
+19960409 change: moved out("\r\n") out of smtp_greet() into callers.
+         this improves the flushing behavior on 221.
+19960328 qmail 0.74, beta.
+19960326 change: changed subdirectory split from 32 to 23.
+19960326 portability problem: some versions of make don't understand
+         that a line with just a tab is blank. impact: couldn't compile
+        under those systems. fix: eliminated extra tab from Makefile.
+        tnx TG.
+19960325 change: added qmail-mailq.
+19960325 change: introduced readsubdir.
+19960325 change: qmail-setup makes split; qmail-check checks split.
+19960325 change: used split in qmail-send, qmail-clean, qmail-queue
+         for mess, info, local, remote.
+19960325 change: fmtqfn now supports split queue subdirectories.
+19960325 cleanup: eliminated cat2s().
+19960325 cleanup: introduced fmtqfn.c. used it in qmail-queue.c,
+         qmail-send.c, qmail-clean.c.
+19960325 change: in protocol between qmail-clean and qmail-send, now
+         using intd/ instead of mess/.
+19960325 change: qmail-queue.c and triggerpull.c now work inside queue
+         subdirectory.
+19960325 change: spawn.c now checks whether message is a regular file.
+19960325 change: spawn.c now allows slashes in messid except at
+         beginning.
+19960325 cleanup: introduced fnmake_split in qmail-send.c.
+19960325 cleanup: eliminated strnum in qmail-send.c in favor of
+         fnmake_{info,todo,mess,chanaddr} and fnmake2_bounce.
+19960325 cleanup: introduced strnum3 in qmail-send.c for the logging
+         uses of strnum.
+19960325 cleanup: in qmail-send.c, getinfo() now takes id argument.
+19960325 cleanup: qmail-send.c now preallocates space for fn, fn2.
+19960325 change: time zone is now -0000 instead of +0000. encouraging
+         DRUMS to use this as an i-don't-know-the-local-time indicator.
+19960324 change: qmail-rspawn.c now calls tcpto_clean().
+19960324 cleanup: spawn.c now calls initialize().
+19960324 change: qmail-setup makes lock/tcpto; qmail-check checks it.
+19960324 change: qmail-remote now quickly skips connect() to a host that
+         seems to be down. tnx BP for pressuring me to get this done.
+19960323 change: in qmail-alias.8, renamed mboxg as mboxrd. tnx RD.
+         idea was popularized by RD in June 1995.
+19960322 cleanup: eliminated subfd_init().
+19960322 change: qbiff now removes the word Subject.
+19960322 change: now /bin/true instead of /dev/null in the generic
+         INSTALL.ids instructions. tnx JPR.
+19960322 change: added hfield_skipname(). tnx RN.
+19960322 bug: qmail-inject did not check whether USER needed quoting.
+         impact: if USER had weird characters, the From address would
+        generally be wrong, unless the user manually set up MAILUSER
+        with proper quoting. fix: qmail-inject sets up a quoted-string
+        if necessary.
+19960322 cleanup: separated out quote_need() in quote.c.
+19960322 cleanup: added stralloc_catb.c. used it in qmail-alias.c,
+         qmail-send.c.
+19960322 change: qmail-send now uses a quadratic retry schedule from
+         birth of each message. this also eliminates clustering.
+19960322 cleanup: separated out nextretry() in qmail-send.c.
+19960322 change: qmail-remote now passes all non-@ addresses through
+         without comment, not just <> and <#>.
+19960322 change: replaced # test with anything@[] test in qmail-inject.
+19960322 change: replaced # with #@[] in qlist.c, qmail-alias.c,
+         qmail-send.c, qreceipt.c.
+19960322 change: qmail-lspawn no longer discards messages to <#>.
+19960322 cleanup: in qlist, used str_diff for <> and <#> tests.
+19960322 change: qmail-alias is now back to testing envelope sender for
+         <> and <#>, rather than things without an @.
+19960321 change: added 8BITMIME support to qmail-smtpd.
+19960321 change: added ESMTP support to qmail-smtpd.
+19960318 change: used NEWSENDER in place of SENDER for |forward.
+19960318 change: added NEWSENDER.
+19960318 change: added HCMSSC support to qmail-alias.c.
+19960318 change: added HCMSSC support to spawn.c.
+19960318 change: added HCMSSC support to qmail-remote.c.
+19960318 change: added HCMSSC support to qmail-smtpd.c.
+19960317 portability problem: SCO requires -lsocket -lnsl. impact:
+         couldn't compile under SCO. fix: added SCO section in
+        make-cmds.sh. tnx JPR. note that this is for OSR 5; 3.2v4.2
+        will need more fixes, and old 3.2 is basically hopeless.
+19960317 bug: newfield_datemake would leave newfield_date alone if it
+         was already initialized, even though qmail-send calls
+        newfield_datemake anew for each bounce. impact: bounce messages
+        would usually have an incorrect Date field. fix: redid
+        newfield_datemake to update newfield_date each time.
+19960317 change: allowed . and @ in 822 phrases; 822 doesn't allow them,
+         but they do show up. tnx to the DRUMS group.
+19960317 change: replaced GMT with +0000 in date822fmt.c. this confuses
+         a few versions of getdate(), but the DRUMS group is going to
+        outlaw GMT, not just recommend against it as in 1123.
+19960317 change: redefined ALIAS_EMPTY to take advantage of . for file
+         deliveries. tnx RN.
+19960317 change: qmail-alias now allows . as well as / to start file
+         deliveries. tnx RN.
+19960317 change: qmail-alias now dies (soft) if .qmail is writable to
+         others, rather than silently ignoring it.
+19960317 change: qmail-alias now dies (soft) if flagforwardonly is
+         violated, rather than silently ignoring the bad instructions.
+19960317 change: qmail-alias now ignores x bit on empty .qmail files.
+19960317 bug: if RCPT gave 4xx and DATA gave 5xx, qmail-rspawn would
+         incorrectly assign a permanent failure to that recipient.
+        impact: in that case, mail would be incorrectly bounced. fix:
+        remove orr > 0 test from qmail-rspawn.c.
+19960310 change: tcp-env now uses signal_uninit(). [sigh]
+19960310 change: tcp-env now specifically unsets HOST and INFO if they
+         are not applicable. just trying to make it more widely usable.
+19960310 cleanup: used byte_* in remoteinfo.c, ipme.c, tcp-env.c.
+19960310 cleanup: added readwrite.h, eliminated sys.h.
+19960310 cleanup: included byte.h in qmail-send.c.
+19960310 cleanup: eliminated i and j from forward.c's main().
+19960310 cleanup: eliminated wstat from qlist.c.
+19960310 cleanup: eliminated die_nomem() parameter in qmail-setup.c.
+19960310 cleanup: eliminated i from qmail-remote's addrmangle().
+19960310 cleanup: added exit.h.
+19960310 cleanup: split ipalloc.c off of ip.c.
+19960310 cleanup: added fmt_strn.c, eliminated fmt_strncpy.c.
+19960310 change: reorganized INSTALL to do some pre-upgrade tests.
+         tnx RN.
+19960310 change: reordered steps in upgrade procedure in INSTALL.
+19960308 change: eliminated ownership test in qmail-alias.c. tnx DS.
+19960304 change: in SECURITY, ``six most recent sendmail security
+         holes'' -> ``seven most recent sendmail security holes.''
+19960303 qmail 0.73, beta.
+19960303 change: added SYSDEPS.
+19960303 cleanup: revamped select.h autoconfiguration.
+19960303 cleanup: revamped fork.h autoconfiguration.
+19960303 cleanup: revamped direntry.h autoconfiguration. target is now
+         direntry.h; auto-hasdrent.h is gone.
+19960303 change: tryflock.c now includes <sys/types.h>, for consistency
+         with lock.c. may affect portability.
+19960302 portability problem: under BSDI, can't set sticky on normal
+         files. dorks. impact: the new qlist doesn't work under BSDI;
+         be glad I test things before release. fix: qmail-alias and
+         qlist now use executable instead of sticky.
+19960302 change: gfrom now quotes >From and >>From etc. as well as From;
+         in other words, I'm switching from mbox format to mboxg format.
+19960302 cleanup: added gfrom.c. used it in qmail-alias.c, maildir2mbox.c.
+19960302 change: addbounce() now substitutes \n\n -> \n/ in reports,
+        and \n -> _ in recips. thus bounces can now be reliably parsed.
+19960302 change: if qmail-send had trouble reading the original message
+        or the list of addresses for a bounce, it used to give up and
+        send a bounce with "Oh no! I had trouble reading the rest of
+        your message" or some such. now it aborts the bounce attempt
+        and tries again later.
+19960302 cleanup: added qqtalk_fail(). used it in qmail-alias.c,
+         qmail-smtpd.c.
+19960302 bug: if mailforward() had trouble reading message (e.g.,
+         because of an I/O error), it marked an error but kept reading.
+        impact: could loop forever. fix: upon error, break.
+19960302 change: maildir2mbox now scans (restrictively) for return-path.
+19960302 change: qbiff now prints subject and body, up to 74 chars.
+19960302 change: added H_SUBJECT to hfield.
+19960302 change: qbiff now puts TO before FROM.
+19960301 cleanup: added fmt_str.c. used it in many places.
+19960301 change: qmail-send now says something if you've told it to exit
+         but it's waiting for some deliveries. tnx RN.
+19960301 change: qmail-alias -n now continues (with warning) if home
+         directory is sticky. tnx RN.
+19960301 change: improved usage messages in qmail-alias.c. tnx RN.
+19960301 change: put limit on length of addresses in qlist.
+19960301 change: added exit 99 support to qmail-alias. tnx RN.
+19960301 change: qmail-alias now exits immediately on temporary or
+         permanent error. rewrote section in qmail-alias.8 accordingly.
+19960301 cleanup: eliminated flagsuccesses from qmail-alias.c.
+19960301 change: added usermap.
+19960301 bug: failure to append to mbox was a permanent error. impact:
+        if mbox was temporarily unopenable (e.g., because fds were
+        low), mail would be incorrectly bounced. fix: failure is now
+        temporary. tnx DS.
+19960229 change: qmail-alias now preserves any envelope sender that
+         doesn't contain an @, not just <> and <#>.
+19960229 cleanup: revamped byte_* interface.
+19960229 cleanup: renamed str_cpy as str_copy.
+19960229 cleanup: added str_chr.c. used it in qbiff.c, qmail-smtpd.c.
+19960229 cleanup: added str_rchr.c. used it in qmail-send.c, quote.c,
+         qmail-remote.c.
+19960229 cleanup: added byte_rchr.c. used it in qmail-smtpd.c, spawn.c.
+19960229 cleanup: used USEREXT instead of RECIPIENT in qsmhook.c.
+19960229 cleanup: used USEREXT instead of RECIPIENT in qbiff.c.
+19960229 cleanup: removed j and k from rewrite() in qmail-send.c.
+19960229 portability problem: under HP-UX 10 and Solaris 2.5, can't
+         setgroups()/setgid() to the system's nogroup/nobody gid. dorks.
+        impact: inetd chokes, so all SMTP connections are rejected; and
+        ``alias'' mail, including postmaster, bounces. fix: in
+        INSTALL.ids, set up a separate powerless gid (tentatively
+        ``nofiles'') for qmaild and alias. tnx DS and PJG.
+19960229 change: qreceipt now uses qqtalk rather than qmail-inject.
+19960229 change: qlist now uses qqtalk rather than qmail-inject.
+19960229 change: incorporated qmail-setup patch from RN for better
+         error messages.
+19960228 change: added LSPAWN_BREAK in conf-unusual.h; used it in
+         lspawn.c. configurability requested by PJG.
+19960228 portability problem: on several systems, including everything
+         from DEC, select() on a pipe reader returns 1 if there aren't
+        any writers yet. pointed out by DS. impact: qmail-send chewed
+        up lots of CPU time. fix: trigger_set() now opens the pipe for
+        writing after opening it for reading. also added trynpbg1; on
+        working systems, no point in wasting the extra fd.
+19960228 change: qmail-alias uses .qmail sticky bit for forwardonly.
+19960228 change: qlist now sets sticky bit on .qmail file.
+19960228 change: un-documented +list.
+19960228 portability problem: on HP-UX and possibly other systems, the
+         supplementary group list does not include the gid. pointed out
+        by DS. impact: on those systems, tryshsgr could incorrectly set
+        hasshsgr; this would prevent qmail-send from running. fix: if
+        tryshsgr sees that getgroups() returns 0, now it actively sets
+        up a supplementary group list. added chkshsgr to make sure the
+        setgroups() will work.
+19960227 cleanup: eliminated GETSHORT in dns.c in favor of getshort().
+19960227 cleanup: deleted h->len < 3 test from qlist.c:dobody. tnx RN.
+19960227 change: replaced ~ with $HOME in INSTALL.mbox.
+19960227 change: added note about setgid-mail bits to INSTALL.mbox.
+19960227 change: added forward.1.
+19960227 change: modified forward to allow multiple addresses.
+19960227 change: modified forward to take an entire address, not just a
+         hostname.
+19960227 change: renamed qrelay as forward.
+19960227 change: added USEREXT support to qmail-alias.
+19960227 change: added -F to sendmail. the need for this was pointed
+         out by RN.
+19960227 change: added 2 bytes of slop in alloc().
+19960227 bug: received_setup() was not allowing space for the final \0.
+         impact: none; the line length is always between 65 and 75
+        characters, which gives at least 45 characters of slop with
+        existing malloc() implementations. fix: leave space. tnx NH.
+        note that the bug here is really in fmt_strncpy, which was
+        written before i was truly free of the curse of libc.a.
+19960227 change: added ALIAS_EMPTY in conf-unusual.h; used it in
+         qmail-alias.c. tnx PJG.
+19960227 change: added SPAWN_NUMD in conf-unusual.h; used it in spawn.c.
+19960227 change: added conf-unusual.h.
+19960227 cleanup: replaced sizeof(short) with 2 in dns.c.
+19960227 portability problem: on an Alpha, long is 64 bits. pointed out
+         by DS. impact: address lookups produced incorrect results on an
+        Alpha; qmail-makectl and qmail-remote failed. fix: replaced
+        sizeof(long) with 4 in dns.c.
+19960227 portability problem: on an Alpha, bzero() is declared properly
+         via sys/time.h. impact: couldn't compile on an Alpha. fix:
+        removed bzero() declaration from select.h. tnx DS.
+19960227 portability problem: under SCO, sys/file.h is not protected.
+         impact: couldn't compile under SCO. fix: include sys/types.h in
+        lock.c. tnx RN.
+19960219 change: added some .qmail-list hints to qlist.1.
+19960219 change: added +list support to qmail-alias.
+19960215 change: added THANKS.
+19960212 bug: foo was not initialized in qrelay.c. impact: depends on
+         the machine; on some machines, no effect; on other machines,
+        guaranteed core dump. fix: initialized foo. tnx DS.
+19960209 qmail 0.72, beta.
+19960209 change: qmail-alias now replaces dot, not slash, with colon.
+        also, qmeopen() makes sure that .qmail file is S_IFREG; I hope
+        this doesn't cause portability problems.
+19960209 change: added success-reporting procedure to INSTALL.
+19960208 change: added VERSION.
+19960208 change: added qlist2.
+19960208 change: revamped qlist interface. tnx RN.
+19960208 change: improved an error message in qlist.c.
+19960208 change: added qrelay. added relay section to HOWTO. tnx DS.
+19960208 cleanup: included substdio.h in qqtalk.h.
+19960207 bug: prioq_delmin() wasn't guaranteeing heap structure on the
+         last element. impact: scheduled passes could have been delayed,
+        conceivably as long as half an hour. fix: prioq_delmin() now
+        checks when it can safely move the last element.
+19960207 change: added maildirmake.1, maildir2mbox.1.
+19960206 change: revised logo paragraph in THOUGHTS.
+19960206 change: replaced nowhere.org with nowhere.mil in examples.
+         nowhere.org is a real domain... [sigh]
+19960206 change: added qreceipt.1.
+19960206 portability problem: IRIX doesn't have vfork. pointed out by
+         DS. impact: couldn't compile under IRIX. fix: added fork.h,
+        tryvfork.c.
+19960206 portability problem: IRIX doesn't have ranlib. pointed out by
+         DS. impact: couldn't compile under IRIX. fix: added IRIX
+        section in make-cmds.sh.
+19960205 cleanup: removed warning from substdio_copy() documentation; in
+         fact, substdio_copy() can be used safely on a fed substdio.
+19960205 change: added qbiff.1.
+19960204 change: implemented localnet. removed relevant paragraph from
+         THOUGHTS. tnx IS.
+19960204 change: in qmail-remote.8, explained the dangers of smarthost.
+         tnx IS.
+19960204 change: implemented virtualdomains wildcards. tnx JLH.
+19960203 change: qmail-send now handles virtualdomains _after_ locals.
+         updated INSTALL.qsmhook appropriately.
+19960203 change: added note to INSTALL.alias about ~ftp, ~www, ~uucp
+         being owned by root.
+19960130 cleanup: in qlist.c, renamed flagremoved as flagwasthere.
+19960130 bug: qmail-send did not pay attention to flagexitasap in
+         pass_dochan(). impact: qmail-send would happily start new
+        deliveries even if it wanted to exit. fix: qmail-send now
+        returns immediately in pass_selprep() and pass_dochan() if
+        flagexitasap.
+19960130 change: in qlist.c and qlist.1, renamed ext as list.
+19960130 change: in qlist.c and qlist.1, renamed manager as owner.
+19960129 qmail 0.71, beta.
+19960129 change: mentioned djb-qmailbeta in README. tnx MWE.
+19960129 change: added a note to INSTALL.mbox making clear that
+         Mailbox is in mbox format. tnx MWE.
+19960129 change: qlist now warns you if it didn't see any cmds. tnx RN.
+19960129 change: incorporated qlist patch from RN to refuse double subs. 
+19960129 change: added qlist.1, contributed by RN. mangled it a bit.
+19960129 bug: comment was not allowed in ``phrase (comment) <route>'';
+         pointed out by RN. impact: some correct address lists could be
+        mis-parsed by qmail-inject or qlist. fix: token.c now allows
+        TOKEN_COMMENT in the appropriate scan.
+19960128 change: added a logo paragraph to THOUGHTS.
+19960127 change: implemented rcpthosts.
+19960127 change: split up some uses of putflush in qmail-remote,
+        qmail-smtpd, spawn.c. eliminated NODELAY and corresponding
+        paragraph in THOUGHTS.
+19960127 change: added quote2(). used it in qmail-alias, qmail-send,
+         qreceipt. now addresses are properly quoted in the From, To,
+        and internal Return-Path of bounces; the From and To of
+        receipts; and the Return-Path/RPLINE of delivered messages.
+        removed relevant paragraph from THOUGHTS.
+19960127 change: in RFCLOOPS, documented fact that Delivered-To address
+         is conventionally not quoted.
+19960127 change: knocked default SMTP timeouts down to 20 minutes.
+19960127 change: added INSTALL.ids. tnx RN.
+19960127 change: in INSTALL, noted that nogroup should already exist.
+19960127 bug: pass_selprep checked pqchan[c] even if pass[c].id. impact:
+        qmail-send wasted CPU time whenever more than one message was
+        waiting on a blocked channel. fix: pass_selprep now checks
+        !pass[c].id.
+19960127 bug: programs invoked from qmail-alias were immune to SIGPIPE.
+         impact: a delivery pipeline such as |yes|head -1000 would loop
+        forever, since yes does not check for write errors. fix: added
+        signal_uninit(). used it before execvp in qmail-alias. [sigh]
+19960127 cleanup: added date822fmt.c. used it in newfield.c, qmail-queue.
+19960127 cleanup: added fmt_uint0.c. used it in myctime.c, newfield.c,
+         qmail-queue.
+19960127 cleanup: added dnsdoe.c. used it in dnscname, dnsfq, dnsip,
+         dnsmxip, dnsptr.
+19960127 cleanup: eliminated temp from dnsfq.c.
+19960127 bug: gen_allocdefs was making assumptions incompatible with the
+         alloc_re interface. impact: qmail-send would dump core if you
+        ran out of memory. fix: changed alloc_re interface.
+19960126 portability problem: some versions of Linux don't have
+         net/route.h. pointed out by RN. impact: couldn't compile under
+        those versions. fix: ipme.c no longer includes net/route.h;
+        hopefully this won't cause new portability problems.
+19960126 change: added chmod instructions to INSTALL and INSTALL.alias.
+         tnx RN.
+19960126 change: INSTALL now refers to the traditional sendmail spot
+         (/usr/lib), not the BSD 4.4 spot (/usr/sbin). tnx RN.
+19960126 change: make auto-uids.h now creates auto-uids.h.tmp first.
+        thus, if someone disobeys the installation instructions, and
+        his make fails to remove targets upon error, he'll still be
+        okay. tnx RN.
+19960126 change: added forgeries.7.
+19960125 cleanup: eliminated flagverbose, flagmetoo in sendmail.
+19960125 cleanup: added substdio_copy.c. used it at several spots.
+19960125 cleanup: added constmap.c. qmail-send now uses constmap for
+         locals and virtualdomains. this will speed things up: no
+        problem now to have thousands of virtual domains. removed
+        relevant paragraph from THOUGHTS.
+19960125 change: added linux section in find-systype. this will affect
+         linux-* systypes. tnx RN for relevant info.
+19960124 change: added -od, -oe, -p, -f to sendmail. the need for
+         these was pointed out by TN.
+19960124 bug: qmail-smtpd was reading from descriptor 1. impact: none;
+         in normal use, both 0 and 1 point to the network. fix: changed
+        1 to 0.
+19960124 bug: qmail-alias treated any .qmail open failure as permanent.
+         impact: if a .qmail file was temporarily unopenable (e.g.,
+        because of NFS), it was incorrectly ignored. fix: qmail-alias
+        now dies QLX_SOFT on any open failure other than ENOENT.
+19960124 change: added freebsd section in find-systype, same as bsd.os
+         section. this will affect freebsd-* systypes.
+19960124 cleanup: find-systype now immediately converts sys to lowercase.
+19960124 change: qmail-setup now copies man pages into /var/qmail/man;
+         qmail-check checks /var/qmail/man. using .0 style, which might
+        cause trouble on various machines, but better than not trying.
+19960124 change: in qmail-remote.c, changed perm_control to temp_control
+         (and D to Z, thanks); thus failure to read control files (e.g.,
+        because of permissions) is now a temporary error.
+19960124 bug: in qmail-remote.c, temp_chdir() used D, not Z. impact: if
+         chdir() to CONF_HOME failed (e.g., because of NFS), message
+        would be bounced. fix: changed D to Z.
+19960124 change: reorganized README.
+19960124 portability problem: Linux has the fifo kernel bug that I had
+         hoped I'd never run into. impact: messages under Linux (and any
+        other systems with this bug) were picked up only in sweeps, not
+        instantly. fix: triggerpull.c now writes a byte (non-blocking)
+        to the fifo. updated INTERNALS accordingly.
+19960124 bug: in qmail-remote.c, if quit() saw a remote write error, it
+         would call writeerr() even though a message report had already
+        been produced. impact: the mess report would include an extra
+        ``ZConnected but communications failed,'' which was confusing
+        to humans. fix: quit() now simply skips the wait-for-QUIT
+        smtpcode() upon write error.
+19960124 portability problem: Linux does not have SIGSYS or SIGEMT.
+         impact: couldn't compile under Linux. fix: added appropriate
+        ifdefs in signal.c.
+19960124 qmail 0.70, beta.
diff --git a/FAQ b/FAQ
new file mode 100644 (file)
index 0000000..214cc1b
--- /dev/null
+++ b/FAQ
@@ -0,0 +1,574 @@
+1. Controlling the appearance of outgoing messages
+1.1. How do I set up host masquerading?
+1.2. How do I set up user masquerading?
+
+2. Routing outgoing messages
+2.1. How do I send local messages to another host?
+2.2. How do I set up a null client?
+2.3. How do I send outgoing mail through UUCP?
+2.4. How do I set up a separate queue for a SLIP/PPP link?
+2.5. How do I deal with ``CNAME lookup failed temporarily''?
+
+3. Routing incoming messages by host
+3.1. How do I receive mail for another host name?
+3.2. How do I set up a virtual domain?
+3.3. How do I set up several virtual domains for one user?
+
+4. Routing incoming messages by user
+4.1. How do I forward unrecognized usernames to another host?
+4.2. How do I set up a mailing list?
+4.3. How do I use majordomo with qmail?
+4.4. How do I use procmail with qmail?
+4.5. How do I use elm's filter with qmail?
+4.6. How do I create aliases with dots?
+
+5. Setting up servers
+5.1. How do I run qmail-smtpd under tcpserver?
+5.2. How do I set up qmail-qmtpd?
+5.3. How do I set up qmail-pop3d?
+5.4. How do I allow selected clients to use this host as a relay?
+5.5. How do I fix up messages from broken SMTP clients?
+
+6. Configuring MUAs to work with qmail
+6.1. How do I make BSD mail generate a Date with the local time zone?
+6.2. How do I stop pine from crashing?
+6.3. How do I make MH work with qmail?
+6.4. How do I stop Sun's dtcm from hanging?
+
+7. Managing the mail system
+7.1. How do I safely stop qmail-send?
+7.2. How do I manually run the queue?
+7.3. How do I rejuvenate a message?
+7.4. How do I organize a big network?
+7.5. How do I back up and restore the queue disk?
+
+8. Miscellany
+8.1. How do I tell qmail to do more deliveries at once?
+8.2. How do I keep a copy of all incoming and outgoing mail messages?
+8.3. How do I switch slowly from sendmail to qmail?
+
+
+
+1. Controlling the appearance of outgoing messages
+
+
+1.1. How do I set up host masquerading? All the users on this host,
+zippy.af.mil, are users on af.mil. When joe sends a message to fred, the
+message should say ``From: joe@af.mil'' and ``To: fred@af.mil'', without
+``zippy'' anywhere.
+
+Answer: echo af.mil > /var/qmail/control/defaulthost; chmod 644
+/var/qmail/control/defaulthost.
+
+
+1.2. How do I set up user masquerading? I'd like my own From lines to
+show boss@af.mil rather than god@heaven.af.mil.
+
+Answer: Add MAILHOST=af.mil and MAILUSER=boss to your environment. To
+override From lines supplied by your MUA, add QMAILINJECT=f to your
+environment.
+
+
+
+2. Routing outgoing messages
+
+
+2.1. How do I send local messages to another host? All the mail for
+af.mil should be delivered to our disk server, pokey.af.mil. I've set up
+an MX from af.mil to pokey.af.mil, but when a user on the af.mil host
+sends a message to boss@af.mil, af.mil tries to deliver it locally. How
+do I stop that?
+
+Answer: Remove af.mil from /var/qmail/control/locals. If qmail-send is
+running, give it a HUP. Make sure the MX is set up properly before you
+do this. Also make sure that pokey can receive mail for af.mil---see
+question 3.1.
+
+
+2.2. How do I set up a null client? I'd like zippy.af.mil to
+send all mail to bigbang.af.mil.
+
+Answer: echo :bigbang.af.mil > /var/qmail/control/smtproutes;
+chmod 644 /var/qmail/control/smtproutes. Disable local delivery as in
+question 2.1. Turn off qmail-smtpd in /etc/inetd.conf.
+
+
+2.3. How do I send outgoing mail through UUCP? I need qmail to send all
+outgoing mail via UUCP to my upstream UUCP site, gonzo.
+
+Answer: Put
+
+   :alias-uucp
+
+into control/virtualdomains and
+
+   |preline -df /usr/bin/uux - -r -gC -a"$SENDER" gonzo!rmail "($EXT2@$HOST)"
+
+into ~alias/.qmail-uucp-default. (For some UUCP software you will need
+to use -d instead of -df. Also, you may need to insert a space between
+-a and "$SENDER" for bounces to work properly.) If qmail-send is
+running, give it a HUP.
+
+
+2.4. How do I set up a separate queue for a SLIP/PPP link?
+
+Answer: Use serialmail (http://pobox.com/~djb/serialmail.html).
+
+
+2.5. How do I deal with ``CNAME lookup failed temporarily''? The log
+showed that a message was deferred for this reason. Why is qmail doing
+CNAME lookups, anyway?
+
+Answer: The SMTP standard does not permit aliased hostnames, so qmail
+has to do a CNAME lookup in DNS for every sender and recipient host. If
+the relevant DNS server is down, qmail defers the message. It will try
+again soon.
+
+
+
+3. Routing incoming messages by host
+
+
+3.1. How do I receive mail for another host name? I'd like our disk
+server, pokey.af.mil, to receive mail addressed to af.mil. I've set up
+an MX from af.mil to pokey.af.mil, but how do I get pokey to treat
+af.mil as a name for the local host?
+
+Answer: Add af.mil to /var/qmail/control/locals and to
+/var/qmail/control/rcpthosts. If qmail-send is running, give it a HUP.
+
+
+3.2. How do I set up a virtual domain? I'd like any mail for
+nowhere.mil, including root@nowhere.mil and postmaster@nowhere.mil and
+so on, to be delivered to Bob. I've set up the MX already.
+
+Answer: Put
+
+   nowhere.mil:bob
+
+into control/virtualdomains. Add nowhere.mil to control/rcpthosts. If
+qmail-send is running, give it a HUP.
+
+Now mail for whatever@nowhere.mil will be delivered locally to
+bob-whatever. Bob can set up ~bob/.qmail-default to catch all the
+possible addresses, ~bob/.qmail-info to catch info@nowhere.mil, etc.
+
+
+3.3. How do I set up several virtual domains for one user? Bob wants
+another virtual domain, everywhere.org, but he wants to handle
+nowhere.mil users and everywhere.org users differently. How can we do
+that without setting up a second account?
+
+Answer: Put two lines into control/virtualdomains:
+
+   nowhere.mil:bob-nowhere
+   everywhere.org:bob-everywhere
+
+Add nowhere.mil and everywhere.org to control/rcpthosts. If qmail-send
+is running, give it a HUP.
+
+Now Bob can set up separate .qmail-nowhere-* and everywhere-* files. He
+can even set up .qmail-nowhere-default and .qmail-everywhere-default.
+
+
+
+4. Routing incoming messages by user
+
+
+4.1. How do I forward unrecognized usernames to another host? I'd like
+to set up a LUSER_RELAY pointing at bigbang.af.mil.
+
+Answer: Put
+
+   | forward "$LOCAL"@bigbang.af.mil
+
+into ~alias/.qmail-default.
+
+
+4.2. How do I set up a mailing list? I'd like me-sos@my.host.name to be
+forwarded to a bunch of people.
+
+Answer: Put a list of addresses into ~me/.qmail-sos, one per line. Then
+incoming mail for me-sos will be forwarded to each of those addresses.
+You should also touch ~me/.qmail-sos-owner so that bounces come back to
+you rather than the original sender. If you want subscriptions to be
+handled automatically, put
+
+   | qlist2 sos my.host.name
+
+into ~me/.qmail-sos-request. Anyone who wants to subscribe can simply
+send a message to me-sos-request@my.host.name.
+
+
+4.3. How do I use majordomo with qmail?
+
+Answer: You need to patch majordomo so that it creates qmail-style
+lists. See ftp://koobera.math.uic.edu/pub/software/majordomo+qmail.gz.
+Exception: qmsmac understands sendmail-style :include: files, so you
+shouldn't patch majordomo if you're using qmsmac.
+
+
+4.4. How do I use procmail with qmail?
+
+Answer: Put 
+
+   | preline procmail
+
+into ~/.qmail. You'll have to use a full path for procmail unless
+procmail is in the system's startup PATH. Note that procmail will try to
+deliver to /usr/spool/mail/$USER by default; to change this, change
+SYSTEM_MBOX in procmail's config.h.
+
+
+4.5. How do I use elm's filter with qmail?
+
+Answer: Put 
+
+   | preline filter
+
+into ~/.qmail. You'll have to use a full path for filter unless filter
+is in the system's startup PATH.
+
+
+4.6. How do I create aliases with dots? I tried setting up
+~alias/.qmail-P.D.Q.Bach, but it doesn't do anything.
+
+Answer: Use .qmail-p:d:q:bach. Dots are converted to colons, and
+uppercase is converted to lowercase.
+
+
+
+5. Setting up servers
+
+
+5.1. How do I run qmail-smtpd under tcpserver? inetd is barfing at high
+loads, cutting off service for ten-minute stretches. I'd also like
+better connection logging.
+
+Answer: First, install the tcpserver program, part of the ucspi-tcp
+package (http://pobox.com/~djb/ucspi-tcp.html). Second, remove the smtp
+line from /etc/inetd.conf, and put the line
+
+   tcpserver -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd &
+
+into your system startup files. Replace 7770 with your qmaild uid, and
+replace 2108 with your nofiles gid. Don't forget the &. The change will
+take effect at your next reboot.
+
+By default, tcpserver allows at most 40 simultaneous qmail-smtpd
+processes. To raise this limit to 400, use tcpserver -c 400. To keep
+track of who's connecting and for how long, run (on two lines)
+
+   tcpserver -v -u 7770 -g 2108 0 smtp /var/qmail/bin/qmail-smtpd \
+   2>&1 | /var/qmail/bin/splogger smtpd 3 &
+
+
+5.2. How do I set up qmail-qmtpd?
+
+Answer: Two steps. First, put a
+
+   qmtp 209/tcp
+
+line into /etc/services. Second, put (all on one line)
+
+   qmtp stream tcp nowait qmaild
+   /var/qmail/bin/tcp-env tcp-env /var/qmail/bin/qmail-qmtpd
+
+into /etc/inetd.conf, and give inetd a HUP.
+
+If you have tcpserver installed, skip the inetd step, and set up
+
+   tcpserver -u 7770 -g 2108 0 qmtp /var/qmail/bin/qmail-qmtpd &
+
+replacing 7770 and 2108 with the qmaild uid and nofiles gid. See
+question 5.1 for more details.
+
+
+5.3. How do I set up qmail-pop3d?
+
+Answer: Four steps. First, install the checkpassword program
+(http://pobox.com/~djb/checkpwd.html). Second, make sure you have a
+
+   pop3 110/tcp
+
+line in /etc/services. Third, put (all on one line)
+
+   pop3 stream tcp nowait root /var/qmail/bin/qmail-popup
+   qmail-popup YOURHOST /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir
+
+into /etc/inetd.conf, and give inetd a HUP; replace YOURHOST with your
+host's fully qualified domain name. Fourth, set up Maildir delivery for
+any user who wants to read mail via POP.
+
+If you have tcpserver installed, skip the inetd step, and set up (on two
+lines)
+
+   tcpserver 0 pop3 /var/qmail/bin/qmail-popup YOURHOST \
+   /bin/checkpassword /var/qmail/bin/qmail-pop3d Maildir &
+
+replacing YOURHOST with your host's fully qualified domain name. See
+question 5.1 for more details.
+
+Security note: pop3d should be used only within a secure network;
+otherwise an eavesdropper can steal passwords.
+
+
+5.4. How do I allow selected clients to use this host as a relay? I see
+that qmail-smtpd rejects messages to any host not listed in
+control/rcpthosts. I know I could entirely disable this feature by
+removing control/rcpthosts, but I want to be more selective.
+
+Answer: Three steps. First, install tcp-wrappers, available separately,
+including hosts_options. Second, change your qmail-smtpd line in
+inetd.conf to
+
+   smtp stream tcp nowait qmaild /usr/local/bin/tcpd
+   /var/qmail/bin/tcp-env /var/qmail/bin/qmail-smtpd
+
+(all on one line) and give inetd a HUP. Third, in tcpd's hosts.allow,
+make a line setting the environment variable RELAYCLIENT to the empty
+string for the selected clients:
+
+   tcp-env: 1.2.3.4, 1.2.3.5: setenv = RELAYCLIENT
+
+Here 1.2.3.4 and 1.2.3.5 are the clients' IP addresses. qmail-smtpd
+ignores control/rcpthosts when RELAYCLIENT is set. (It also appends
+RELAYCLIENT to each envelope recipient address. See question 5.5 for an
+application.)
+
+Alternative procedure, if you are using tcpserver: Install tcpcontrol
+(http://pobox.com/~djb/tcpcontrol.html). Create /etc/tcp.smtp containing
+
+   1.2.3.6:allow,RELAYCLIENT=""
+   127.:allow,RELAYCLIENT=""
+
+to allow clients with IP addresses 1.2.3.6 and 127.*. Run
+
+   tcpmakectl /etc/tcp.smtp.cdb /etc/tcp.smtp.tmp < /etc/tcp.smtp
+
+Finally, insert
+
+   tcpcontrol /etc/tcp.smtp.cdb
+
+before /var/qmail/bin/qmail-smtpd in your tcpserver line.
+
+
+5.5. How do I fix up messages from broken SMTP clients?
+
+Answer: Three steps. First, put
+
+   | [ "@$HOST" = "@fixme" ] || ( echo Permission denied; exit 100 )
+   | qmail-inject -f "$SENDER" -- "$EXT2"
+
+into ~alias/.qmail-fixup-default. Second, put
+
+   fixme:fixup
+
+into /var/qmail/control/virtualdomains, and give qmail-send a HUP.
+Third, follow the procedure in question 5.4, but set RELAYCLIENT to the
+string ``@fixme'':
+
+   tcp-env: 1.2.3.6, 1.2.3.7: setenv = RELAYCLIENT @fixme
+
+Here 1.2.3.6 and 1.2.3.7 are the clients' IP addresses. If you are using
+tcpserver and tcpcontrol instead of inetd and tcpd, put
+
+   1.2.3.6:allow,RELAYCLIENT="@fixme"
+   1.2.3.7:allow,RELAYCLIENT="@fixme"
+
+into /etc/tcp.smtp, and run tcpmakectl as in question 5.4.
+
+
+
+6. Configuring MUAs to work with qmail
+
+
+6.1. How do I make BSD mail generate a Date with the local time zone?
+When I send mail, I'd rather use the local time zone than GMT, since
+some MUAs don't know how to display Date in the receiver's time zone.
+
+Answer: Put
+
+   set sendmail=/var/qmail/bin/datemail
+
+into your .mailrc or your system-wide Mail.rc. Beware that BSD mail is
+neither secure nor reliable.
+
+
+6.2. How do I stop pine from crashing? When I ask any version of pine
+past 3.91 to send mail, it crashes.
+
+Answer: Put
+
+   sendmail-path=/usr/lib/sendmail -oem -oi -t
+
+into /usr/local/lib/pine.conf. (This will work with sendmail too.)
+Beware that pine is neither secure nor reliable.
+
+
+6.3. How do I make MH work with qmail?
+
+Answer: Put
+
+   postproc: /usr/mh/lib/spost
+
+into each user's .mh_profile. (This will work with sendmail too.) Beware
+that MH is neither secure nor reliable.
+
+
+6.4. How do I stop Sun's dtcm from hanging?
+
+Answer: There is a novice programming error in dtcm, known as ``failure
+to close the output side of the pipe in the child.'' Sun has, at the
+time of this writing, not yet provided a patch. Sorry.
+
+
+
+7. Managing the mail system
+
+
+7.1. How do I safely stop qmail-send? Back when we were running
+sendmail, it was always tricky to kill sendmail without risking the loss
+of current deliveries; what should I do with qmail-send?
+
+Answer: Go ahead and kill the qmail-send process. It will shut down
+cleanly. Wait for ``exiting'' to show up in the log. To restart it, run
+qmail-start the same way as it's run from your system boot scripts.
+
+
+7.2. How do I manually run the queue? I'd like qmail to try delivering
+all the remote messages right now.
+
+Answer: Give the qmail-send process an ALRM.
+
+
+7.3. How do I rejuvenate a message? Somebody broke into Eric's computer
+again; it's going to be down for at least another two days. I know Eric
+has been expecting an important message---in fact, I see it sitting here
+in /var/qmail/queue/mess/15/26902. It's been in the queue for six days;
+how can I make sure it isn't bounced tomorrow?
+
+Answer: Just touch /var/qmail/queue/info/15/26902. (This is the only
+form of queue modification that's safe while qmail is running.)
+
+
+7.4. How do I organize a big network? I have a lot of machines, and I
+don't know where to start.
+
+Answer: First, choose the domain name where your users will receive
+mail. This is normally the shortest domain name you control. If you are
+in charge of *.movie.edu, you can use addresses like joe@movie.edu.
+
+Second, choose the machine that will know what to do with different
+users at movie.edu. Set up a host name in DNS for this machine:
+
+   mailhost.movie.edu IN A 1.2.3.4
+   4.3.2.1.in-addr.arpa IN PTR mailhost.movie.edu
+
+Here 1.2.3.4 is the IP address of that machine.
+
+Third, make a list of machines where mail should end up. For example, if
+mail for Bob should end up on Bob's workstation, put Bob's workstation
+onto the list. For each of these machines, set up a host name in DNS:
+
+   bobshost.movie.edu IN A 1.2.3.7
+   7.3.2.1.in-addr.arpa IN PTR bobshost.movie.edu
+
+Fourth, install qmail on bobshost.movie.edu. qmail will automatically
+configure itself to accept messages for bob@bobshost.movie.edu and
+deliver them to ~bob/Mailbox on bobshost. Do the same for the other
+machines where mail should end up.
+
+Fifth, install qmail on mailhost.movie.edu. Put
+
+   movie.edu:alias-movie
+
+into control/virtualdomains on mailhost. Then forward bob@movie.edu to
+bob@bobshost.movie.edu, by putting
+
+   bob@bobshost.movie.edu
+
+into ~alias/.qmail-movie-bob. Do the same for other users.
+
+Sixth, put movie.edu into control/rcpthosts on mailhost.movie.edu, so
+that mailhost.movie.edu will accept messages for users at movie.edu.
+
+Seventh, set up an MX record in DNS to deliver movie.edu messages to
+mailhost:
+
+   movie.edu IN MX 10 mailhost.movie.edu
+
+Eighth, on all your machines, put movie.edu into control/defaulthost.
+
+
+7.5. How do I back up and restore the queue disk?
+
+Answer: You can't.
+
+One difficulty is that you can't get a consistent snapshot of the queue
+while qmail-send is running. Another difficulty is that messages in the
+queue must have filenames that match their inode numbers.
+
+However, the big problem is that backups---even twice-daily backups---
+are far too unreliable for mail. If your disk dies, there will be very
+little overlap between the messages saved in the last backup and the
+messages that were lost.
+
+There are several ways to add real reliability to a mail server. Battery
+backups will keep your server alive, letting you park the disk to avoid
+a head crash, when the power goes out. Solid-state disks have their own
+battery backups. RAID boxes let you replace dead disks without losing
+any data.
+
+
+
+8. Miscellany
+
+
+8.1. How do I tell qmail to do more deliveries at once? It's running
+only 20 parallel qmail-remote processes.
+
+Answer: Decide how many deliveries you want to allow at once. Put that
+number into control/concurrencyremote. Restart qmail-send as in question
+7.1. If your system has resource limits, make sure you set the
+descriptors limit to at least double the concurrency plus 5; otherwise
+you'll get lots of unnecessary deferrals whenever a big burst of mail
+shows up. Note that qmail also imposes a compile-time concurrency limit,
+120 by default; this is set in conf-spawn.
+
+
+8.2. How do I keep a copy of all incoming and outgoing mail messages?
+
+Answer: Set QUEUE_EXTRA to "Tlog\0" and QUEUE_EXTRALEN to 5 in extra.h.
+Recompile qmail. Put ./msg-log into ~alias/.qmail-log.
+
+You can also use QUEUE_EXTRA to, e.g., record the Message-ID of every
+message: run
+
+   | awk '/^$/ { exit } /^[mM][eE][sS][sS][aA][gG][eE]-/ { print }'
+
+from ~alias/.qmail-log.
+
+
+8.3. How do I switch slowly from sendmail to qmail? I'm thinking of
+moving the heaven.af.mil network over to qmail, but first I'd like to
+give my users a chance to try out qmail without affecting current
+sendmail deliveries. We're using NFS.
+
+Answer: Find a host in your network, say pc.heaven.af.mil, that isn't
+running an SMTP server. (If addresses at pc.heaven.af.mil are used, you
+should already have an MX pointing pc.heaven.af.mil to your mail hub.)
+
+Set up a new MX record pointing lists.heaven.af.mil to pc.heaven.af.mil.
+Install qmail on pc.heaven.af.mil. Replace pc with lists in the control
+files. Make the qmail man pages available on all your machines.
+
+Now tell your users about qmail. A user can forward joe@heaven.af.mil to
+joe@lists.heaven.af.mil to get ~/Mailbox delivery; he can set up .qmail
+files; he can start running his own mailing lists @lists.heaven.af.mil.
+
+When you're ready to turn sendmail off, you can set up pc.heaven.af.mil
+as your new mail hub. Add heaven.af.mil to control/locals, and change
+the heaven.af.mil MX to point to pc.heaven.af.mil. Make sure you leave
+lists.heaven.af.mil in control/locals so that transition addresses will
+continue to work.
diff --git a/FILES b/FILES
new file mode 100644 (file)
index 0000000..b581fac
--- /dev/null
+++ b/FILES
@@ -0,0 +1,401 @@
+BLURB
+BLURB2
+BLURB3
+BLURB4
+README
+FAQ
+INSTALL
+INSTALL.alias
+INSTALL.boot
+INSTALL.ctl
+INSTALL.ids
+INSTALL.mbox
+INSTALL.qsmhook
+UPGRADE
+THOUGHTS
+TODO
+THANKS
+CHANGES
+RFCHCSC
+RFCLOOPS
+RFCMXPS
+RFCNETSTR
+RFCNRUDT
+RFCQMTP
+RFCQSBMF
+RFCVERP
+SECURITY
+INTERNALS
+FILES
+VERSION
+SYSDEPS
+TARGETS
+Makefile
+conf-break
+auto_break.h
+conf-spawn
+auto_spawn.h
+chkspawn.c
+conf-split
+auto_split.h
+conf-patrn
+auto_patrn.h
+conf-users
+conf-groups
+auto_uids.h
+auto_usera.h
+extra.h
+addresses.5
+condredirect.1
+dot-qmail.9
+envelopes.5
+forgeries.7
+forward.1
+maildir2mbox.1
+maildirmake.1
+maildirwatch.1
+mailsubj.1
+mbox.5
+preline.1
+qbiff.1
+qlist.1
+qmail-clean.8
+qmail-command.8
+qmail-control.9
+qmail-getpw.9
+qmail-header.5
+qmail-inject.8
+qmail-limits.9
+qmail-local.8
+qmail-log.5
+qmail-lspawn.8
+qmail-newu.9
+qmail-pop3d.8
+qmail-popup.8
+qmail-pw2u.9
+qmail-qmtpd.8
+qmail-qread.8
+qmail-qstat.8
+qmail-queue.8
+qmail-remote.8
+qmail-rspawn.8
+qmail-send.9
+qmail-showctl.8
+qmail-smtpd.8
+qmail-start.9
+qmail-tcpto.8
+qmail-upgrade.9
+qmail-users.9
+qmail.7
+qreceipt.1
+splogger.8
+tcp-env.1
+qmail-clean.c
+qmail-config.sh
+qmail-getpw.c
+qmail-hier.c
+qmail-inject.c
+qmail-local.c
+qmail-lspawn.c
+qmail-newu.c
+qmail-pop3d.c
+qmail-popup.c
+qmail-pw2u.c
+qmail-qmtpd.c
+qmail-qread.c
+qmail-qstat.sh
+qmail-queue.c
+qmail-remote.c
+qmail-rspawn.c
+qmail-send.c
+qmail-showctl.c
+qmail-smtpd.c
+qmail-start.c
+qmail-tcpto.c
+spawn.c
+dnscname.c
+dnsfq.c
+dnsip.c
+dnsmxip.c
+dnsptr.c
+hostname.c
+ipmeprint.c
+tcp-env.c
+sendmail.c
+qlist.c
+qreceipt.c
+qsmhook.c
+qbiff.c
+forward.c
+preline.c
+predate.c
+condredirect.c
+maildirmake.c
+maildir2mbox.c
+maildirwatch.c
+splogger.c
+qail.sh
+elq.sh
+pinq.sh
+qlist2.sh
+qmail-upq.sh
+datemail.sh
+mailsubj.sh
+qlx.h
+constmap.h
+constmap.c
+dnsdoe.h
+dnsdoe.c
+fmtqfn.h
+fmtqfn.c
+gfrom.h
+gfrom.c
+myctime.h
+myctime.c
+newfield.h
+newfield.c
+qsutil.h
+qsutil.c
+readsubdir.h
+readsubdir.c
+received.h
+received.c
+tcpto.h
+tcpto.c
+tcpto_clean.c
+trigger.h
+trigger.c
+triggerpull.h
+triggerpull.c
+trynpbg1.c
+trysyslog.c
+conf-cc
+conf-ld
+find-systype.sh
+make-compile.sh
+make-load.sh
+make-makelib.sh
+trycpp.c
+warn-auto.sh
+auto-str.c
+auto-int.c
+auto-int8.c
+auto-gid.c
+auto-uid.c
+install.c
+instcheck.c
+alloc.3
+alloc.h
+alloc.c
+alloc_re.c
+case.3
+case.h
+case_diffb.c
+case_diffs.c
+case_lowerb.c
+case_lowers.c
+case_starts.c
+cdb.3
+cdb.h
+cdb_hash.c
+cdb_seek.c
+cdb_unpack.c
+cdbmake.h
+cdbmake_add.c
+cdbmake_hash.c
+cdbmake_pack.c
+cdbmss.h
+cdbmss.c
+coe.3
+coe.h
+coe.c
+fd.h
+fd_copy.3
+fd_copy.c
+fd_move.3
+fd_move.c
+fifo_make.3
+fifo.h
+fifo.c
+trymkffo.c
+fork.h1
+fork.h2
+tryvfork.c
+now.3
+now.h
+now.c
+open.h
+open_append.c
+open_excl.c
+open_read.c
+open_trunc.c
+open_write.c
+seek.h
+seek_cur.c
+seek_end.c
+seek_set.c
+seek_trunc.c
+conf-qmail
+auto_qmail.h
+qmail.h
+qmail.c
+gen_alloc.h
+gen_allocdefs.h
+stralloc.3
+stralloc.h
+stralloc_eady.c
+stralloc_pend.c
+stralloc_copy.c
+stralloc_opyb.c
+stralloc_opys.c
+stralloc_cat.c
+stralloc_catb.c
+stralloc_cats.c
+stralloc_arts.c
+strerr.h
+strerr_sys.c
+strerr_die.c
+substdio.h
+substdio.c
+substdi.c
+substdo.c
+substdio_copy.c
+subfd.h
+subfderr.c
+subfdouts.c
+subfdout.c
+subfdins.c
+subfdin.c
+readwrite.h
+exit.h
+timeoutconn.h
+timeoutconn.c
+timeoutread.h
+timeoutread.c
+timeoutwrite.h
+timeoutwrite.c
+remoteinfo.h
+remoteinfo.c
+uint32.h1
+uint32.h2
+tryulong32.c
+wait.3
+wait.h
+wait_pid.c
+wait_nohang.c
+trywaitp.c
+sig.h
+sig_alarm.c
+sig_block.c
+sig_catch.c
+sig_pause.c
+sig_pipe.c
+sig_child.c
+sig_term.c
+sig_hup.c
+sig_misc.c
+sig_bug.c
+trysgact.c
+trysgprm.c
+env.3
+env.h
+env.c
+envread.c
+byte.h
+byte_chr.c
+byte_copy.c
+byte_cr.c
+byte_diff.c
+byte_rchr.c
+byte_zero.c
+str.h
+str_chr.c
+str_cpy.c
+str_diff.c
+str_diffn.c
+str_len.c
+str_rchr.c
+str_start.c
+lock.h
+lock_ex.c
+lock_exnb.c
+lock_un.c
+tryflock.c
+getln.3
+getln.h
+getln.c
+getln2.3
+getln2.c
+sgetopt.3
+sgetopt.h
+sgetopt.c
+subgetopt.3
+subgetopt.h
+subgetopt.c
+error.3
+error_str.3
+error_temp.3
+error.h
+error.c
+error_str.c
+error_temp.c
+fmt.h
+fmt_str.c
+fmt_strn.c
+fmt_uint.c
+fmt_uint0.c
+fmt_ulong.c
+scan.h
+scan_ulong.c
+scan_8long.c
+scan_nbblong.c
+slurpclose.h
+slurpclose.c
+quote.h
+quote.c
+hfield.h
+hfield.c
+headerbody.h
+headerbody.c
+token822.h
+token822.c
+control.h
+control.c
+datetime.3
+datetime.h
+datetime.c
+datetime_un.c
+prioq.h
+prioq.c
+date822fmt.h
+date822fmt.c
+dns.h
+dns.c
+trylsock.c
+tryrsolv.c
+ip.h
+ip.c
+ipalloc.h
+ipalloc.c
+select.h1
+select.h2
+trysysel.c
+ndelay.h
+ndelay.c
+ndelay_off.c
+direntry.3
+direntry.h1
+direntry.h2
+trydrent.c
+prot.h
+prot.c
+chkshsgr.c
+warn-shsgr
+tryshsgr.c
+ipme.h
+ipme.c
+trysalen.c
+maildir.5
+maildir.h
+maildir.c
+tcp-environ.5
diff --git a/INSTALL b/INSTALL
new file mode 100644 (file)
index 0000000..314e94a
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,181 @@
+SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and
+information generally), the qmail system comes with NO WARRANTY. It's
+much more secure and reliable than sendmail, but that's not saying much.
+
+
+Things you have to decide before starting:
+
+* The qmail home directory, normally /var/qmail. To change this
+directory, edit conf-qmail now.
+
+* The names of the qmail users and the qmail groups. To change these
+names, edit conf-users and conf-groups now.
+
+
+Installation steps that won't interfere with sendmail:
+
+ 1. Create the qmail home directory:
+       # mkdir /var/qmail
+ 2. Read INSTALL.ids. You must set up the qmail group and the qmail
+    users before compiling the programs.
+ 3. Compile the programs:
+       # make
+ 4. Create the formatted man pages, *.0:
+       # make man
+ 5. Create the qmail directory tree:
+       # make setup
+ 6. Run instcheck to make sure it doesn't print any warnings:
+       # make check
+ 7. Read INSTALL.ctl and FAQ. Minimal survival command:
+       # ./qmail-config
+ 8. Read INSTALL.alias. Minimal survival command:
+       # (cd ~alias; touch .qmail-postmaster .qmail-mailer-daemon .qmail-root)
+       # chmod 644 ~alias/.qmail*
+ 9. Read INSTALL.mbox.
+10. Read qmail-upgrade.0. This is what your users will need to know
+    about the switch from sendmail to qmail.
+
+
+Pre-upgrade tests:
+
+11. Enable deliveries of messages injected into qmail:
+       # env - PATH="/var/qmail/bin:$PATH" \
+       qmail-start ./Mailbox splogger qmail &
+    Make sure to include the ./ in ./Mailbox.
+12. Look for a
+            qmail: running
+    line in syslog. qmail-send always prints either ``cannot start'' or
+    ``running''. (The big number is a splogger timestamp.)
+13. Do a ps and look for the qmail daemons. There should be four of
+    them, all idle: qmail-send, running as qmails; qmail-lspawn, running
+    as root; qmail-rspawn, running as qmailr; and qmail-clean, running
+    as qmailq. You will also see the splogger process.
+14. Local-local test: Send yourself an empty message. (Replace ``me''
+    with your username. Make sure to include the ``to:'' colon.)
+       % echo to: me | /var/qmail/bin/qmail-inject
+    The message will show up immediately in ~/Mailbox, and syslog will
+    show something like this:
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20345 uid 666
+            qmail: starting delivery 1: msg 53 to local me@domain 
+            qmail: delivery 1: success: did_1+0+0/
+            qmail: end msg 53 
+    (53 is an inode number; 20345 is a process ID; your numbers will
+    probably be different.)
+15. Local-error test: Send a message to a nonexistent local address.
+       % echo to: nonexistent | /var/qmail/bin/qmail-inject
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20351 uid 666
+            qmail: starting delivery 2: msg 53 to local nonexistent@domain
+            qmail: delivery 2: failure: No_such_address.__#5.1.1_/
+           qmail: bounce msg 53 qp 20357
+            qmail: end msg 53 
+            qmail: new msg 54 
+            qmail: info msg 54: bytes 743 from <> qp 20357 uid 666
+            qmail: starting delivery 3: msg 54 to local me@domain
+            qmail: delivery 3: success: did_1+0+0/
+            qmail: end msg 54 
+    You will now have a bounce message in ~/Mailbox.
+16. Local-remote test: Send an empty message to your account on another
+    machine.
+       % echo to: me@wherever | /var/qmail/bin/qmail-inject
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20372 uid 666
+            qmail: starting delivery 4: msg 53 to remote me@wherever
+            qmail: delivery 4: success: 1.2.3.4_accepted_message./...
+            qmail: end msg 53 
+    There will be a pause between ``starting delivery'' and ``success'';
+    SMTP is slow. Check that the message is in your mailbox on the other
+    machine.
+17. Local-postmaster test: Send mail to postmaster, any capitalization.
+       % echo to: POSTmaster | /var/qmail/bin/qmail-inject
+    Look for the message in ~alias/Mailbox.
+18. Double-bounce test: Send a message with a completely bad envelope.
+       % /var/qmail/bin/qmail-inject -f nonexistent
+       To: unknownuser
+       Subject: testing
+
+       This is a test. This is only a test.
+       %
+    (Use end-of-file, not dot, to end the message.) Look for the double
+    bounce in ~alias/Mailbox.
+19. Group membership test:
+       % cat > ~me/.qmail-groups
+       |groups >> MYGROUPS; exit 0
+       % /var/qmail/bin/qmail-inject me-groups < /dev/null
+       % cat ~me/MYGROUPS
+    MYGROUPS will show your normal gid and nothing else. (Under Solaris,
+    make sure to use /usr/ucb/groups; /usr/bin/groups is broken.)
+
+
+Upgrading from sendmail to qmail:
+
+20. Read INSTALL.boot. You must replace the sendmail invocation in your
+    boot scripts with an appropriate qmail invocation.
+21. Kill the sendmail daemon. You should first kill -STOP the daemon; if
+    any children are running, you should kill -CONT, wait, kill -STOP
+    again, and repeat ad nauseam. If there aren't any children, kill
+    -TERM and then kill -CONT.
+22. Replace sendmail with a link to qmail's ``sendmail'' wrapper:
+       # mv /usr/lib/sendmail /usr/lib/sendmail.bak
+       # ln -s /var/qmail/bin/sendmail /usr/lib/sendmail
+23. Set up qmail-smtpd in /etc/inetd.conf (all on one line):
+            smtp stream tcp nowait qmaild /var/qmail/bin/tcp-env
+           tcp-env /var/qmail/bin/qmail-smtpd
+    Also comment out comsat in /etc/inetd.conf.
+24. Reboot. (Or kill -HUP your inetd and make sure the qmail daemons
+    are running.)
+25. Try to flush the sendmail queue:
+       # /usr/lib/sendmail.bak -q
+    You can safely run sendmail.bak -q (or even sendmail.bak -q15m)
+    while qmail is running. Do this until the sendmail queue is empty.
+    This may take several days.
+26. Disable all the sendmail and binmail programs in your system. The
+    safest approach is to chmod 0 everything. Some locations to check:
+    /usr/sbin/sendmail, /usr/lib/sendmail.bak, /usr/lib/sendmail.mx,
+    /bin/mail, /usr/libexec/mail.local.
+27. Make sure that ``mail'' still invokes a reasonable mailer. Under
+    SVR4 you may want to link mail to mailx.
+
+
+Post-upgrade tests (can be done immediately after step 24):
+
+28. SMTP server test: Forge some mail locally via SMTP.
+       % telnet 127.0.0.1 25
+       Trying 127.0.0.1...
+       Connected to 127.0.0.1.
+       Escape character is '^]'.
+       220 domain ESMTP
+       helo dude
+       250-domain
+       250-PIPELINING
+       250 8BITMIME
+       mail <me@domain>
+       250 ok
+       rcpt <me@domain>
+       250 ok
+       data
+       354 go ahead
+       Subject: testing
+       
+       This is a test.
+       .
+       250 ok 812345679 qp 12345
+       quit
+       221 domain
+       Connection closed by foreign host.
+       %
+    Look for the message in your mailbox.
+29. Remote-local test: Send yourself some mail from another machine.
+30. Remote-error test: I think you can figure this one out.
+31. UA test: Try sending mail, first to a local account, then to a
+    remote account, with your normal user agent.
+32. Remote-postmaster test: Send mail from another machine to
+    PoStMaStEr@domain. Look for the message in ~alias/Mailbox.
+
+
+That's it! To report success:
+       % ( echo 'First M. Last'; cat `cat SYSDEPS` ) \
+         | mail djb-qst@koobera.math.uic.edu
+Replace First M. Last with your name. If you have questions about qmail,
+contact qmail@pobox.com.
diff --git a/INSTALL.alias b/INSTALL.alias
new file mode 100644 (file)
index 0000000..5721da1
--- /dev/null
@@ -0,0 +1,40 @@
+qmail lets each user control all addresses of the form user-anything.
+Addresses that don't start with a username are controlled by a special
+user, alias. Delivery instructions for foo go into ~alias/.qmail-foo;
+delivery instructions for user-foo go into ~user/.qmail-foo. See
+qmail-upgrade.0 and dot-qmail.0 for the full story.
+
+qmail doesn't have any built-in support for /etc/aliases. If you have a
+big /etc/aliases and you'd like to keep it, install the qmsmac package,
+available separately. /etc/aliases should already include the aliases
+discussed below---Postmaster, MAILER-DAEMON, and root.
+
+If you don't have a big /etc/aliases, you'll find it easier to use
+qmail's native alias mechanism. Here's a checklist of aliases you should
+set up right now.
+
+* Postmaster. You're not an Internet citizen if this address doesn't
+work. Simply touch (and chmod 644) ~alias/.qmail-postmaster; any mail
+for Postmaster will be delivered to ~alias/Mailbox.
+
+* MAILER-DAEMON. Not required, but users sometimes respond to bounce
+messages. Touch (and chmod 644) ~alias/.qmail-mailer-daemon.
+
+* root. Under qmail, root never receives mail. Your system may generate
+mail messages to root every night; if you don't have an alias for root,
+those messages will bounce. (They'll end up double-bouncing to the
+postmaster.) Set up an alias for root in ~alias/.qmail-root. .qmail
+files are similar to .forward files, but beware that they are strictly
+line-oriented---see dot-qmail.0 for details.
+
+* Other non-user accounts. Under qmail, non-user accounts don't get
+mail; ``user'' means a non-root account that owns ~account. Set up
+aliases for any non-user accounts that normally receive mail.
+
+Note that special accounts such as ftp, www, and uucp should always have
+home directories owned by root.
+
+* Default. If you want, you can touch ~alias/.qmail-default to catch
+everything else. Beware: this will also catch typos and other addresses
+that should probably be bounced instead. It won't catch addresses that
+start with a user name---the user can set up his own ~/.qmail-default.
diff --git a/INSTALL.boot b/INSTALL.boot
new file mode 100644 (file)
index 0000000..0010288
--- /dev/null
@@ -0,0 +1,16 @@
+The qmail daemons have to be restarted whenever your system reboots.
+Meanwhile, sendmail doesn't have to be started any more. Here's what you
+should do.
+
+Find sendmail in your boot scripts. It's usually in either /etc/rc or
+/etc/init.d/sendmail. It looks like
+
+        sendmail -bd -q15m
+
+-q15m means it should run the queue every 15 minutes; you may see a
+different number. Comment out this line, and replace it with
+
+        env - PATH="/var/qmail/bin:$PATH" \
+       csh -cf 'qmail-start ./Mailbox splogger qmail &'
+
+That's it. (Make sure you include the ./ and the &.)
diff --git a/INSTALL.ctl b/INSTALL.ctl
new file mode 100644 (file)
index 0000000..f493fcf
--- /dev/null
@@ -0,0 +1,29 @@
+As you've seen, qmail has essentially no pre-compilation configuration.
+You should never have to recompile it unless you want to change the
+qmail home directory, usernames, or uids.
+
+qmail does allow quite a bit of easy post-installation configuration. If
+you care how your machine greets other machines via SMTP, for example,
+you can put an appropriate line into /var/qmail/control/smtpgreeting.
+
+But this is all optional---if control/smtpgreeting doesn't exist, qmail
+will do something reasonable by default. You shouldn't worry much about
+configuration right now. You can always come back and tune things later.
+
+There's one big exception. You MUST tell qmail your hostname. The easy
+way to do this is to run the qmail-config script:
+
+   # ./qmail-config
+
+qmail-config finds your fully-qualified hostname in DNS and puts it into
+control/me. It also selects good defaults for a few other controls.
+
+(Why doesn't qmail do these lookups on the fly? This was a deliberate
+design decision. qmail does all its local functions---header rewriting,
+checking if a recipient is local, etc.---without talking to the network.
+The point is that qmail can continue accepting and delivering local mail
+even if your network connection goes down.)
+
+Next, read through FAQ for information on setting up optional features
+like masquerading. If you really want to learn right now what all the
+configuration possibilities are, see qmail-control.0.
diff --git a/INSTALL.ids b/INSTALL.ids
new file mode 100644 (file)
index 0000000..e0e4ee2
--- /dev/null
@@ -0,0 +1,59 @@
+Here's how to set up the qmail groups and the qmail users.
+
+On some systems there are commands that make this easy. Solaris:
+
+   # groupadd nofiles
+   # useradd -g nofiles -d /var/qmail/alias alias
+   # useradd -g nofiles -d /var/qmail qmaild
+   # useradd -g nofiles -d /var/qmail qmaill
+   # useradd -g nofiles -d /var/qmail qmailp
+   # groupadd qmail
+   # useradd -g qmail -d /var/qmail qmailq
+   # useradd -g qmail -d /var/qmail qmailr
+   # useradd -g qmail -d /var/qmail qmails
+
+BSDI 2.0:
+
+   # addgroup nofiles
+   # adduser -g nofiles -H/var/qmail/alias -G,,, -s/dev/null -P'*' alias
+   # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaild
+   # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmaill
+   # adduser -g nofiles -H/var/qmail -G,,, -s/dev/null -P'*' qmailp
+   # addgroup qmail
+   # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailq
+   # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmailr
+   # adduser -g qmail -H/var/qmail -G,,, -s/dev/null -P'*' qmails
+
+AIX:
+
+   # mkgroup -A nofiles
+   # mkuser pgrp=nofiles home=/var/qmail/alias shell=/bin/true alias
+   # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaild
+   # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmaill
+   # mkuser pgrp=nofiles home=/var/qmail shell=/bin/true qmailp
+   # mkgroup -A qmail
+   # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailq
+   # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmailr
+   # mkuser pgrp=qmail home=/var/qmail shell=/bin/true qmails
+
+On other systems, you will have to edit /etc/group and /etc/passwd
+manually. First add two new lines to /etc/group, something like
+
+        qmail:*:2107:
+        nofiles:*:2108:
+
+where 2107 and 2108 are different from the other gids in /etc/group.
+Next (using vipw) add six new lines to /etc/passwd, something like
+
+        alias:*:7790:2108::/var/qmail/alias:/bin/true
+        qmaild:*:7791:2108::/var/qmail:/bin/true
+        qmaill:*:7792:2108::/var/qmail:/bin/true
+        qmailp:*:7793:2108::/var/qmail:/bin/true
+        qmailq:*:7794:2107::/var/qmail:/bin/true
+        qmailr:*:7795:2107::/var/qmail:/bin/true
+        qmails:*:7796:2107::/var/qmail:/bin/true
+
+where 7790 through 7796 are _new_ uids, 2107 is the qmail gid, and 2108
+is the nofiles gid. Make sure you use the nofiles gid for qmaild,
+qmaill, qmailp, and alias, and the qmail gid for qmailq, qmailr, and
+qmails.
diff --git a/INSTALL.mbox b/INSTALL.mbox
new file mode 100644 (file)
index 0000000..a62a181
--- /dev/null
@@ -0,0 +1,112 @@
+The qmail package includes a local delivery agent, qmail-local, which
+provides user-controlled mailing lists, cross-host alias loop detection,
+and many other important qmail features.
+
+There's one part of qmail-local that you need to know about right now:
+qmail-local doesn't support an insecure central mail spool. It delivers
+mail by default into ~user/Mailbox (in mbox format).
+
+This file explains what you should do to deal with this change. It also
+points out some reasons that you might want to make an even bigger
+change, switching from mbox format to a new format, maildir.
+
+If you desperately don't want to change anything, see INSTALL.qsmhook.
+
+
+Contents:
+1. Throw away /usr/spool/mail!
+2. The trouble with mbox
+3. Sun's Network F_\bail_\bu_\bre System
+
+
+1. Throw away /usr/spool/mail!
+
+/usr/spool/mail, often called /var/spool/mail or /var/mail, is a
+security disaster. A user's mailbox belongs in his home directory, not a
+shared directory. Even if you don't install qmail, you should destroy
+/usr/spool/mail. This takes four steps:
+
+   A. Convince your local mailer to deliver to ~user/Mailbox. If you're
+   using something like procmail, this is easy---just change SYSTEM_MBOX
+   in config.h. If you're installing qmail, you don't have to do
+   anything. Otherwise, take a look at hlfsd from
+   ftp.cs.columbia.edu/pub/amd.
+
+   B. Move each /usr/spool/mail/user to ~user/Mailbox. For safety, do
+   this in single-user mode---you don't want to risk corrupting
+   mailboxes. (qmail makes it easy to turn off deliveries temporarily:
+   just kill the qmail-send daemon. But you aren't running qmail yet.)
+   When you're done, remove /usr/spool/mail.
+
+   C. Put ``setenv MAIL $HOME/Mailbox'' in your system-wide .cshrc,
+   ``MAIL=$HOME/Mailbox; export MAIL'' in your system-wide .profile,
+   ``inbox-path=Mailbox'' in your system-wide pine.conf. If you're using
+   qpopper 2.2, you'll have to recompile with -DHOMEDIRMAIL in CFLAGS
+   and with /.mail changed to /Mailbox in pop_dropcopy.c. If you're
+   using elm on a multiuser system, you'll have to recompile elm with
+   "mailbox" changed to "Mailbox" around line 388 of newmbox.c.
+
+   D. Announce the change.
+
+Some vendors, in a misguided attempt to solve the security problems of
+/usr/spool/mail, have made all MUAs (e.g., /usr/ucb/Mail) setgid mail.
+After you get rid of /usr/spool/mail, you can also disable those
+setgid-mail bits.
+
+
+2. The trouble with mbox
+
+The mbox format---the format of ~user/Mailbox, understood by BSD Mail
+and lots of other MUAs---is inherently unreliable.
+
+Think about it: what happens if the system crashes while a program is
+appending a new message to ~user/Mailbox? The message will be truncated.
+Even worse, if it was truncated in the middle of a line, it will end up
+being merged with the next message! Sure, the mailer understands that it
+wasn't successful, so it'll try delivering the message again later, but
+it can't fix your corrupted mbox.
+
+Other formats, such as mh folders, are just as unreliable.
+
+qmail supports maildir, a crashproof format for incoming mail messages.
+maildir is fast and easy for MUAs to use. Even better, maildir works
+wonders over NFS---see below.
+
+I don't want to cram maildir down people's throats, so it's not the
+default. Nevertheless, I encourage you to start asking for maildir
+versions of your favorite MUAs, and to switch over to maildir as soon as
+you can.
+
+WARNING: qmail uses flock() to lock ~user/Mailbox. This agrees with the
+modern mail.local locking choice. If your MUA doesn't use flock(), your
+best bet is to switch to maildir, and to set up synchronous maildir2mbox
+execution, as described below.
+
+
+3. Sun's Network F_\bail_\bu_\bre System
+
+Anyone who tells you that mail can be safely delivered in mbox format
+over NFS is pulling your leg---as explained above, mbox format is
+inherently unreliable even on a single machine.
+
+Anyway, NFS is the most unreliable computing environment ever invented,
+and qmail doesn't even pretend to support mbox over NFS.
+
+You should switch to maildir, which works fine over NFS without any
+locking. You can safely read your mail over NFS if it's in maildir
+format. Any number of machines can deliver mail to you at the same time.
+(On the other hand, for efficiency, it's better to get NFS out of the
+picture---your mail should be delivered on the server that contains your
+home directory.)
+
+Here's how to set up qmail to use maildir for your incoming mail:
+
+   % maildirmake $HOME/Maildir
+   % echo ./Maildir/ > ~/.qmail
+
+Make sure you include the trailing slash on Maildir/.
+
+Until your MUA supports maildir, you'll probably want to convert maildir
+format to (gaaack) mbox format. I've supplied a maildir2mbox utility
+that does the trick, along with some tiny qail and elq and pinq wrappers
+that call maildir2mbox before calling Mail or elm or pine.
diff --git a/INSTALL.qsmhook b/INSTALL.qsmhook
new file mode 100644 (file)
index 0000000..952fcd2
--- /dev/null
@@ -0,0 +1,53 @@
+You can set up qmail to use the same local delivery agent as sendmail,
+through a mechanism called qsmhook. This file says how.
+
+Before you do this, may I ask why? If you simply don't want the hassle
+of moving user mailboxes from /usr/spool/mail to ~, please reconsider---
+/usr/spool/mail has always been a big security problem. See, for
+example, CERT advisory 95:02.
+
+If you're trying to preserve /etc/aliases and ~user/.forward, you're
+looking the wrong way---those are handled by sendmail internally, not by
+the local delivery agent. You can use your old /etc/aliases with qmail
+by installing the qmsmac package.
+
+Perhaps you've set up an advanced agent like procmail. But most people
+use procmail for nothing more than sorting mail into several mailboxes;
+and that's much easier with qmail's local forwarding mechanism, which
+gives each user control over user-anything. If you have a few users who
+really do need procmail, they can easily run procmail from their own
+.qmail files.
+
+Do you still want to set up qsmhook? Send me some e-mail and let me know
+why. Perhaps I can provide something for you in a future qmail release.
+
+Here's what to do. First, tack ``:alias'' onto the end of each address
+in /var/qmail/control/locals, and put the results into
+/var/qmail/control/virtualdomains. For example, if control/locals has
+
+   localhost
+   silverton.berkeley.edu
+
+then control/virtualdomains should now have (without extra spaces)
+
+   localhost:alias
+   silverton.berkeley.edu:alias
+
+Second, cp /dev/null control/locals.
+
+Third, put a line into ~alias/.qmail-default, based on sendmail's Mlocal
+line. For example, if sendmail.cf has
+
+   Mlocal, P=/bin/mail, F=lsDFMPrmn, S=10, R=20, A=mail -d $u
+
+then ~alias/.qmail-default should have
+
+   |qsmhook -x alias- -lsDFMPmn /bin/mail -r %g -d %u
+
+As another example, if sendmail.cf has
+
+   Mlocal, P=/usr/lib/mail.local, F=flsSDFMmnP, S=10, R=20, A=mail.local -d $u
+
+then ~alias/.qmail-default should have
+
+   |qsmhook -x alias- -lsDFMmnP /usr/lib/mail.local -f %g -d %u
diff --git a/INTERNALS b/INTERNALS
new file mode 100644 (file)
index 0000000..ee80517
--- /dev/null
+++ b/INTERNALS
@@ -0,0 +1,155 @@
+1. Overview
+
+Here's the data flow in the qmail suite:
+
+ qmail-smtpd --- qmail-queue --- qmail-send --- qmail-rspawn --- qmail-remote
+               /                     |      \
+qmail-inject _/                 qmail-clean  \_ qmail-lspawn --- qmail-local
+
+Every message is added to a central queue directory by qmail-queue.
+qmail-queue is invoked as needed, usually by qmail-inject for locally
+generated messages, qmail-smtpd for messages received through SMTP,
+qmail-local for forwarded messages, or qmail-send for bounce messages.
+
+Every message is then delivered by qmail-send, in cooperation with
+qmail-lspawn and qmail-rspawn, and cleaned up by qmail-clean. These four
+programs are long-running daemons.
+
+The queue is designed to be crashproof, provided that the underlying
+filesystem is crashproof. All cleanups are handled by qmail-send and
+qmail-clean without human intervention. See section 6 for more details.
+
+
+2. Queue structure
+
+Each message in the queue is identified by a unique number, let's say
+457. The queue is organized into several directories, each of which may
+contain files related to message 457:
+
+   mess/457: the message
+   todo/457: the envelope: where the message came from, where it's going
+   intd/457: the envelope, under construction by qmail-queue
+   info/457: the envelope sender address, after preprocessing
+   local/457: local envelope recipient addresses, after preprocessing
+   remote/457: remote envelope recipient addresses, after preprocessing
+   bounce/457: permanent delivery errors
+
+Here are all possible states for a message. + means a file exists; -
+means it does not exist; ? means it may or may not exist.
+
+   S1. -mess -intd -todo -info -local -remote -bounce
+   S2. +mess -intd -todo -info -local -remote -bounce
+   S3. +mess +intd -todo -info -local -remote -bounce
+   S4. +mess ?intd +todo ?info ?local ?remote -bounce (queued)
+   S5. +mess -intd -todo +info ?local ?remote ?bounce (preprocessed)
+
+Guarantee: If mess/457 exists, it has inode number 457.
+
+
+3. How messages enter the queue
+
+To add a message to the queue, qmail-queue first creates a file in a
+separate directory, pid/, with a unique name. The filesystem assigns
+that file a unique inode number. qmail-queue looks at that number, say
+457. By the guarantee above, message 457 must be in state S1.
+
+qmail-queue renames pid/whatever as mess/457, moving to S2. It writes
+the message to mess/457. It then creates intd/457, moving to S3, and
+writes the envelope information to intd/457.
+
+Finally qmail-queue creates a new link, todo/457, for intd/457, moving
+to S4. At that instant the message has been successfully queued, and
+qmail-queue leaves it for further handling by qmail-send.
+
+qmail-queue starts a 24-hour timer before touching any files, and
+commits suicide if the timer expires.
+
+
+4. How queued messages are preprocessed
+
+Once a message has been queued, qmail-send must decide which recipients
+are local and which recipients are remote. It may also rewrite some
+recipient addresses.
+
+When qmail-send notices todo/457, it knows that message 457 is in S4. It
+removes info/457, local/457, and remote/457 if they exist. Then it reads
+through todo/457. It creates info/457, possibly local/457, and possibly
+remote/457. When it is done, it removes intd/457. The message is still
+in S4 at this point. Finally qmail-send removes todo/457, moving to S5.
+At that instant the message has been successfully preprocessed.
+
+
+5. How preprocessed messages are delivered
+
+Messages at S5 are handled as follows. Each address in local/457 and
+remote/457 is marked either NOT DONE or DONE.
+
+   DONE: The message was successfully delivered, or the last delivery
+         attempt met with permanent failure. Either way, qmail-send
+        should not attempt further delivery to this address.
+
+   NOT DONE: If there have been any delivery attempts, they have all
+             met with temporary failure. Either way, qmail-send should
+             try delivery in the future.
+
+qmail-send may at its leisure try to deliver a message to a NOT DONE
+address. If the message is successfully delivered, qmail-send marks the
+address as DONE. If the delivery attempt meets with permanent failure,
+qmail-send first appends a note to bounce/457, creating bounce/457 if
+necessary; then it marks the address as DONE.
+
+qmail-send may handle bounce/457 at any time, as follows: it (1) injects
+a new bounce message, created from bounce/457 and mess/457; (2) deletes
+bounce/457.
+
+When all addresses in local/457 are DONE, qmail-send deletes local/457.
+Same for remote/457. 
+
+When local/457 and remote/457 are gone, qmail-send eliminates the
+message, as follows. First, if bounce/457 exists, qmail-send handles it
+as described above. Once bounce/457 is definitely gone, qmail-send
+deletes info/457, moving to S2, and finally mess/457, moving to S1.
+
+
+6. Cleanups
+
+If the computer crashes while qmail-queue is trying to queue a message,
+or while qmail-send is eliminating a message, the message may be left in
+state S2 or S3.
+
+When qmail-send sees a message in state S2 or S3---other than one
+it is currently eliminating!---where mess/457 is more than 36 hours old,
+it deletes intd/457 if that exists, then deletes mess/457. Note that any
+qmail-queue handling the message must be dead.
+
+Similarly, when qmail-send sees a file in the pid/ directory that is
+more than 36 hours old, it deletes it.
+
+Cleanups are not necessary if the computer crashes while qmail-send is
+delivering a message. At worst a message may be delivered twice. (There
+is no way for a distributed mail system to eliminate the possibility of
+duplication. What if an SMTP connection is broken just before the server
+acknowledges successful receipt of the message? The client must assume
+the worst and send the message again. Similarly, if the computer crashes
+just before qmail-send marks a message as DONE, the new qmail-send must
+assume the worst and send the message again. The usual solutions in the
+database literature---e.g., keeping log files---amount to saying that
+it's the recipient's computer's job to discard duplicate messages.)
+
+
+7. Further notes
+
+Currently info/457 serves two purposes: first, it records the envelope
+sender; second, its modification time is used to decide when a message
+has been in the queue too long. In the future info/457 may store more
+information. Any non-backwards-compatible changes will be identified by
+version numbers.
+
+When qmail-queue has successfully placed a message into the queue, it
+pulls a trigger offered by qmail-send. Here is the current triggering
+mechanism: lock/trigger is a named pipe. Before scanning todo/,
+qmail-send opens lock/trigger O_NDELAY for reading. It then selects for
+readability on lock/trigger. qmail-queue pulls the trigger by writing a
+byte O_NDELAY to lock/trigger. This makes lock/trigger readable and
+wakes up qmail-send. Before scanning todo/ again, qmail-send closes and
+reopens lock/trigger.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..96a352c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2181 @@
+SHELL=/bin/sh
+
+default: it
+
+addresses.0: \
+addresses.5
+       nroff -man addresses.5 > addresses.0
+
+alloc.a: \
+makelib alloc.o alloc_re.o
+       ./makelib alloc.a alloc.o alloc_re.o
+
+alloc.o: \
+compile alloc.c alloc.h alloc.c error.h alloc.c
+       ./compile alloc.c
+
+alloc_re.o: \
+compile alloc_re.c alloc.h alloc_re.c byte.h alloc_re.c
+       ./compile alloc_re.c
+
+auto-ccld.sh: \
+conf-cc conf-ld warn-auto.sh
+       ( cat warn-auto.sh; \
+       echo CC=\'`head -1 conf-cc`\'; \
+       echo LD=\'`head -1 conf-ld`\' \
+       ) > auto-ccld.sh
+
+auto-gid: \
+load auto-gid.o substdio.a error.a str.a fs.a
+       ./load auto-gid substdio.a error.a str.a fs.a 
+
+auto-gid.o: \
+compile auto-gid.c auto-gid.c auto-gid.c subfd.h substdio.h subfd.h \
+auto-gid.c substdio.h substdio.h auto-gid.c readwrite.h auto-gid.c \
+exit.h auto-gid.c scan.h auto-gid.c fmt.h auto-gid.c
+       ./compile auto-gid.c
+
+auto-int: \
+load auto-int.o substdio.a error.a str.a fs.a
+       ./load auto-int substdio.a error.a str.a fs.a 
+
+auto-int.o: \
+compile auto-int.c substdio.h auto-int.c readwrite.h auto-int.c \
+exit.h auto-int.c scan.h auto-int.c fmt.h auto-int.c
+       ./compile auto-int.c
+
+auto-int8: \
+load auto-int8.o substdio.a error.a str.a fs.a
+       ./load auto-int8 substdio.a error.a str.a fs.a 
+
+auto-int8.o: \
+compile auto-int8.c substdio.h auto-int8.c readwrite.h auto-int8.c \
+exit.h auto-int8.c scan.h auto-int8.c fmt.h auto-int8.c
+       ./compile auto-int8.c
+
+auto-str: \
+load auto-str.o substdio.a error.a str.a
+       ./load auto-str substdio.a error.a str.a 
+
+auto-str.o: \
+compile auto-str.c substdio.h auto-str.c readwrite.h auto-str.c \
+exit.h auto-str.c
+       ./compile auto-str.c
+
+auto-uid: \
+load auto-uid.o substdio.a error.a str.a fs.a
+       ./load auto-uid substdio.a error.a str.a fs.a 
+
+auto-uid.o: \
+compile auto-uid.c auto-uid.c auto-uid.c subfd.h substdio.h subfd.h \
+auto-uid.c substdio.h substdio.h auto-uid.c readwrite.h auto-uid.c \
+exit.h auto-uid.c scan.h auto-uid.c fmt.h auto-uid.c
+       ./compile auto-uid.c
+
+auto_break.c: \
+auto-str conf-break
+       ./auto-str auto_break \
+       "`head -1 conf-break`" > auto_break.c
+
+auto_break.o: \
+compile auto_break.c
+       ./compile auto_break.c
+
+auto_patrn.c: \
+auto-int8 conf-patrn
+       ./auto-int8 auto_patrn `head -1 conf-patrn` > auto_patrn.c
+
+auto_patrn.o: \
+compile auto_patrn.c
+       ./compile auto_patrn.c
+
+auto_qmail.c: \
+auto-str conf-qmail
+       ./auto-str auto_qmail `head -1 conf-qmail` > auto_qmail.c
+
+auto_qmail.o: \
+compile auto_qmail.c
+       ./compile auto_qmail.c
+
+auto_spawn.c: \
+auto-int conf-spawn
+       ./auto-int auto_spawn `head -1 conf-spawn` > auto_spawn.c
+
+auto_spawn.o: \
+compile auto_spawn.c
+       ./compile auto_spawn.c
+
+auto_split.c: \
+auto-int conf-split
+       ./auto-int auto_split `head -1 conf-split` > auto_split.c
+
+auto_split.o: \
+compile auto_split.c
+       ./compile auto_split.c
+
+auto_uids.c: \
+auto-uid auto-gid conf-users conf-groups
+       ( ./auto-uid auto_uida `head -1 conf-users` \
+       &&./auto-uid auto_uidd `head -2 conf-users | tail -1` \
+       &&./auto-uid auto_uidl `head -3 conf-users | tail -1` \
+       &&./auto-uid auto_uido `head -4 conf-users | tail -1` \
+       &&./auto-uid auto_uidp `head -5 conf-users | tail -1` \
+       &&./auto-uid auto_uidq `head -6 conf-users | tail -1` \
+       &&./auto-uid auto_uidr `head -7 conf-users | tail -1` \
+       &&./auto-uid auto_uids `head -8 conf-users | tail -1` \
+       &&./auto-gid auto_gidq `head -1 conf-groups` \
+       &&./auto-gid auto_gidn `head -2 conf-groups | tail -1` \
+       ) > auto_uids.c
+
+auto_uids.o: \
+compile auto_uids.c
+       ./compile auto_uids.c
+
+auto_usera.c: \
+auto-str conf-users
+       ./auto-str auto_usera `head -1 conf-users` > auto_usera.c
+
+auto_usera.o: \
+compile auto_usera.c
+       ./compile auto_usera.c
+
+byte_chr.o: \
+compile byte_chr.c byte.h byte_chr.c
+       ./compile byte_chr.c
+
+byte_copy.o: \
+compile byte_copy.c byte.h byte_copy.c
+       ./compile byte_copy.c
+
+byte_cr.o: \
+compile byte_cr.c byte.h byte_cr.c
+       ./compile byte_cr.c
+
+byte_diff.o: \
+compile byte_diff.c byte.h byte_diff.c
+       ./compile byte_diff.c
+
+byte_rchr.o: \
+compile byte_rchr.c byte.h byte_rchr.c
+       ./compile byte_rchr.c
+
+byte_zero.o: \
+compile byte_zero.c byte.h byte_zero.c
+       ./compile byte_zero.c
+
+case.a: \
+makelib case_diffb.o case_diffs.o case_lowerb.o case_lowers.o \
+case_starts.o
+       ./makelib case.a case_diffb.o case_diffs.o case_lowerb.o \
+       case_lowers.o case_starts.o
+
+case_diffb.o: \
+compile case_diffb.c case.h case_diffb.c
+       ./compile case_diffb.c
+
+case_diffs.o: \
+compile case_diffs.c case.h case_diffs.c
+       ./compile case_diffs.c
+
+case_lowerb.o: \
+compile case_lowerb.c case.h case_lowerb.c
+       ./compile case_lowerb.c
+
+case_lowers.o: \
+compile case_lowers.c case.h case_lowers.c
+       ./compile case_lowers.c
+
+case_starts.o: \
+compile case_starts.c case.h case_starts.c
+       ./compile case_starts.c
+
+cdb.a: \
+makelib cdb_hash.o cdb_unpack.o cdb_seek.o
+       ./makelib cdb.a cdb_hash.o cdb_unpack.o cdb_seek.o
+
+cdb_hash.o: \
+compile cdb_hash.c cdb.h uint32.h cdb.h cdb_hash.c
+       ./compile cdb_hash.c
+
+cdb_seek.o: \
+compile cdb_seek.c cdb_seek.c cdb_seek.c cdb.h uint32.h cdb.h \
+cdb_seek.c
+       ./compile cdb_seek.c
+
+cdb_unpack.o: \
+compile cdb_unpack.c cdb.h uint32.h cdb.h cdb_unpack.c
+       ./compile cdb_unpack.c
+
+cdbmake.a: \
+makelib cdbmake_pack.o cdbmake_hash.o cdbmake_add.o
+       ./makelib cdbmake.a cdbmake_pack.o cdbmake_hash.o \
+       cdbmake_add.o
+
+cdbmake_add.o: \
+compile cdbmake_add.c cdbmake.h uint32.h cdbmake.h cdbmake_add.c
+       ./compile cdbmake_add.c
+
+cdbmake_hash.o: \
+compile cdbmake_hash.c cdbmake.h uint32.h cdbmake.h cdbmake_hash.c
+       ./compile cdbmake_hash.c
+
+cdbmake_pack.o: \
+compile cdbmake_pack.c cdbmake.h uint32.h cdbmake.h cdbmake_pack.c
+       ./compile cdbmake_pack.c
+
+cdbmss.o: \
+compile cdbmss.c readwrite.h cdbmss.c seek.h cdbmss.c alloc.h \
+cdbmss.c cdbmss.h cdbmake.h uint32.h cdbmake.h cdbmss.h substdio.h \
+cdbmss.h cdbmss.c
+       ./compile cdbmss.c
+
+check: \
+it man conf-qmail
+       ./qmail-hier | ./instcheck `head -1 conf-qmail`
+
+chkshsgr: \
+load chkshsgr.o
+       ./load chkshsgr 
+
+chkshsgr.o: \
+compile chkshsgr.c exit.h chkshsgr.c
+       ./compile chkshsgr.c
+
+chkspawn: \
+load chkspawn.o substdio.a error.a str.a fs.a auto_spawn.o
+       ./load chkspawn substdio.a error.a str.a fs.a auto_spawn.o 
+
+chkspawn.o: \
+compile chkspawn.c substdio.h chkspawn.c subfd.h substdio.h \
+substdio.h subfd.h chkspawn.c fmt.h chkspawn.c select.h select.h \
+select.h select.h chkspawn.c exit.h chkspawn.c auto_spawn.h \
+chkspawn.c
+       ./compile chkspawn.c
+
+clean: \
+TARGETS
+       rm -f `cat TARGETS`
+
+coe.o: \
+compile coe.c coe.c coe.h coe.c
+       ./compile coe.c
+
+compile: \
+make-compile warn-auto.sh systype
+       ( cat warn-auto.sh; ./make-compile "`cat systype`" ) > \
+       compile
+       chmod 755 compile
+
+condredirect: \
+load condredirect.o qmail.o fd.a sig.a wait.a seek.a env.a alloc.a \
+substdio.a error.a str.a auto_qmail.o
+       ./load condredirect qmail.o fd.a sig.a wait.a seek.a env.a \
+       alloc.a substdio.a error.a str.a auto_qmail.o 
+
+condredirect.0: \
+condredirect.1
+       nroff -man condredirect.1 > condredirect.0
+
+condredirect.o: \
+compile condredirect.c sig.h condredirect.c readwrite.h \
+condredirect.c exit.h condredirect.c env.h condredirect.c error.h \
+condredirect.c fork.h condredirect.c wait.h condredirect.c seek.h \
+condredirect.c qmail.h substdio.h qmail.h condredirect.c stralloc.h \
+gen_alloc.h stralloc.h condredirect.c subfd.h substdio.h substdio.h \
+subfd.h condredirect.c substdio.h substdio.h condredirect.c
+       ./compile condredirect.c
+
+constmap.o: \
+compile constmap.c constmap.h constmap.c alloc.h constmap.c case.h \
+constmap.c
+       ./compile constmap.c
+
+control.o: \
+compile control.c readwrite.h control.c open.h control.c getln.h \
+control.c stralloc.h gen_alloc.h stralloc.h control.c substdio.h \
+control.c error.h control.c control.h control.c alloc.h control.c \
+scan.h control.c
+       ./compile control.c
+
+date822fmt.o: \
+compile date822fmt.c datetime.h date822fmt.c fmt.h date822fmt.c \
+date822fmt.h date822fmt.c
+       ./compile date822fmt.c
+
+datemail: \
+warn-auto.sh datemail.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh datemail.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > datemail
+       chmod 755 datemail
+
+datetime.a: \
+makelib datetime.o datetime_un.o
+       ./makelib datetime.a datetime.o datetime_un.o
+
+datetime.o: \
+compile datetime.c datetime.h datetime.c
+       ./compile datetime.c
+
+datetime_un.o: \
+compile datetime_un.c datetime.h datetime_un.c
+       ./compile datetime_un.c
+
+direntry.h: \
+compile trydrent.c direntry.h1 direntry.h2
+       ( ./compile trydrent.c >/dev/null 2>&1 \
+       && cat direntry.h2 || cat direntry.h1 ) > direntry.h
+       rm -f trydrent.o
+
+dns.lib: \
+tryrsolv.c compile load socket.lib dns.o ipalloc.o ip.o stralloc.a \
+alloc.a error.a fs.a str.a
+       ( ( ./compile tryrsolv.c && ./load tryrsolv dns.o \
+       ipalloc.o ip.o stralloc.a alloc.a error.a fs.a str.a \
+       -lresolv `cat socket.lib` ) >/dev/null 2>&1 \
+       && echo -lresolv || exit 0 ) > dns.lib
+       rm -f tryrsolv.o tryrsolv
+
+dns.o: \
+compile dns.c dns.c dns.c dns.c dns.c dns.c dns.c dns.c ip.h dns.c \
+ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h dns.c fmt.h dns.c \
+alloc.h dns.c str.h dns.c stralloc.h gen_alloc.h stralloc.h dns.c \
+dns.h dns.c case.h dns.c
+       ./compile dns.c
+
+dnscname: \
+load dnscname.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \
+substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load dnscname dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a  `cat dns.lib` `cat \
+       socket.lib`
+
+dnscname.o: \
+compile dnscname.c substdio.h dnscname.c subfd.h substdio.h \
+substdio.h subfd.h dnscname.c stralloc.h gen_alloc.h stralloc.h \
+dnscname.c dns.h dnscname.c dnsdoe.h dnscname.c readwrite.h \
+dnscname.c exit.h dnscname.c
+       ./compile dnscname.c
+
+dnsdoe.o: \
+compile dnsdoe.c substdio.h dnsdoe.c subfd.h substdio.h substdio.h \
+subfd.h dnsdoe.c exit.h dnsdoe.c dns.h dnsdoe.c dnsdoe.h dnsdoe.c
+       ./compile dnsdoe.c
+
+dnsfq: \
+load dnsfq.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \
+substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load dnsfq dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a  `cat dns.lib` `cat \
+       socket.lib`
+
+dnsfq.o: \
+compile dnsfq.c substdio.h dnsfq.c subfd.h substdio.h substdio.h \
+subfd.h dnsfq.c stralloc.h gen_alloc.h stralloc.h dnsfq.c dns.h \
+dnsfq.c dnsdoe.h dnsfq.c ip.h dnsfq.c ipalloc.h ip.h ip.h ipalloc.h \
+gen_alloc.h ipalloc.h dnsfq.c exit.h dnsfq.c
+       ./compile dnsfq.c
+
+dnsip: \
+load dnsip.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \
+substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load dnsip dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a  `cat dns.lib` `cat \
+       socket.lib`
+
+dnsip.o: \
+compile dnsip.c substdio.h dnsip.c subfd.h substdio.h substdio.h \
+subfd.h dnsip.c stralloc.h gen_alloc.h stralloc.h dnsip.c dns.h \
+dnsip.c dnsdoe.h dnsip.c ip.h dnsip.c ipalloc.h ip.h ip.h ipalloc.h \
+gen_alloc.h ipalloc.h dnsip.c exit.h dnsip.c
+       ./compile dnsip.c
+
+dnsmxip: \
+load dnsmxip.o dns.o dnsdoe.o ip.o ipalloc.o now.o stralloc.a alloc.a \
+substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load dnsmxip dns.o dnsdoe.o ip.o ipalloc.o now.o \
+       stralloc.a alloc.a substdio.a error.a str.a fs.a  `cat \
+       dns.lib` `cat socket.lib`
+
+dnsmxip.o: \
+compile dnsmxip.c substdio.h dnsmxip.c subfd.h substdio.h substdio.h \
+subfd.h dnsmxip.c stralloc.h gen_alloc.h stralloc.h dnsmxip.c fmt.h \
+dnsmxip.c dns.h dnsmxip.c dnsdoe.h dnsmxip.c ip.h dnsmxip.c ipalloc.h \
+ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h dnsmxip.c now.h datetime.h \
+now.h dnsmxip.c exit.h dnsmxip.c
+       ./compile dnsmxip.c
+
+dnsptr: \
+load dnsptr.o dns.o dnsdoe.o ip.o ipalloc.o stralloc.a alloc.a \
+substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load dnsptr dns.o dnsdoe.o ip.o ipalloc.o stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a  `cat dns.lib` `cat \
+       socket.lib`
+
+dnsptr.o: \
+compile dnsptr.c substdio.h dnsptr.c subfd.h substdio.h substdio.h \
+subfd.h dnsptr.c stralloc.h gen_alloc.h stralloc.h dnsptr.c str.h \
+dnsptr.c scan.h dnsptr.c dns.h dnsptr.c dnsdoe.h dnsptr.c ip.h \
+dnsptr.c exit.h dnsptr.c
+       ./compile dnsptr.c
+
+dot-qmail.0: \
+dot-qmail.5
+       nroff -man dot-qmail.5 > dot-qmail.0
+
+dot-qmail.5: \
+dot-qmail.9 conf-break conf-spawn
+       cat dot-qmail.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > dot-qmail.5
+
+elq: \
+warn-auto.sh elq.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh elq.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > elq
+       chmod 755 elq
+
+env.a: \
+makelib env.o envread.o
+       ./makelib env.a env.o envread.o
+
+env.o: \
+compile env.c str.h env.c alloc.h env.c env.h env.c
+       ./compile env.c
+
+envelopes.0: \
+envelopes.5
+       nroff -man envelopes.5 > envelopes.0
+
+envread.o: \
+compile envread.c env.h envread.c str.h envread.c
+       ./compile envread.c
+
+error.a: \
+makelib error.o error_str.o error_temp.o
+       ./makelib error.a error.o error_str.o error_temp.o
+
+error.o: \
+compile error.c error.c error.h error.c
+       ./compile error.c
+
+error_str.o: \
+compile error_str.c error_str.c error.h error_str.c
+       ./compile error_str.c
+
+error_temp.o: \
+compile error_temp.c error_temp.c error.h error_temp.c
+       ./compile error_temp.c
+
+fd.a: \
+makelib fd_copy.o fd_move.o
+       ./makelib fd.a fd_copy.o fd_move.o
+
+fd_copy.o: \
+compile fd_copy.c fd_copy.c fd.h fd_copy.c
+       ./compile fd_copy.c
+
+fd_move.o: \
+compile fd_move.c fd.h fd_move.c
+       ./compile fd_move.c
+
+fifo.o: \
+compile fifo.c fifo.c fifo.c hasmkffo.h fifo.c fifo.h fifo.c
+       ./compile fifo.c
+
+find-systype: \
+find-systype.sh auto-ccld.sh
+       cat auto-ccld.sh find-systype.sh > find-systype
+       chmod 755 find-systype
+
+fmt_str.o: \
+compile fmt_str.c fmt.h fmt_str.c
+       ./compile fmt_str.c
+
+fmt_strn.o: \
+compile fmt_strn.c fmt.h fmt_strn.c
+       ./compile fmt_strn.c
+
+fmt_uint.o: \
+compile fmt_uint.c fmt.h fmt_uint.c
+       ./compile fmt_uint.c
+
+fmt_uint0.o: \
+compile fmt_uint0.c fmt.h fmt_uint0.c
+       ./compile fmt_uint0.c
+
+fmt_ulong.o: \
+compile fmt_ulong.c fmt.h fmt_ulong.c
+       ./compile fmt_ulong.c
+
+fmtqfn.o: \
+compile fmtqfn.c fmtqfn.h fmtqfn.c fmt.h fmtqfn.c auto_split.h \
+fmtqfn.c
+       ./compile fmtqfn.c
+
+forgeries.0: \
+forgeries.7
+       nroff -man forgeries.7 > forgeries.0
+
+fork.h: \
+compile load tryvfork.c fork.h1 fork.h2
+       ( ( ./compile tryvfork.c && ./load tryvfork ) >/dev/null \
+       2>&1 \
+       && cat fork.h2 || cat fork.h1 ) > fork.h
+       rm -f tryvfork.o tryvfork
+
+forward: \
+load forward.o stralloc.a alloc.a qmail.o fd.a wait.a sig.a env.a \
+substdio.a error.a str.a auto_qmail.o
+       ./load forward stralloc.a alloc.a qmail.o fd.a wait.a \
+       sig.a env.a substdio.a error.a str.a auto_qmail.o 
+
+forward.0: \
+forward.1
+       nroff -man forward.1 > forward.0
+
+forward.o: \
+compile forward.c sig.h forward.c readwrite.h forward.c exit.h \
+forward.c env.h forward.c qmail.h substdio.h qmail.h forward.c \
+stralloc.h gen_alloc.h stralloc.h forward.c subfd.h substdio.h \
+substdio.h subfd.h forward.c substdio.h substdio.h forward.c
+       ./compile forward.c
+
+fs.a: \
+makelib fmt_str.o fmt_strn.o fmt_uint.o fmt_uint0.o fmt_ulong.o \
+scan_ulong.o scan_8long.o scan_nbblong.o
+       ./makelib fs.a fmt_str.o fmt_strn.o fmt_uint.o fmt_uint0.o \
+       fmt_ulong.o scan_ulong.o scan_8long.o scan_nbblong.o
+
+getln.a: \
+makelib getln.o getln2.o
+       ./makelib getln.a getln.o getln2.o
+
+getln.o: \
+compile getln.c substdio.h getln.c byte.h getln.c stralloc.h \
+gen_alloc.h stralloc.h getln.c getln.h getln.c
+       ./compile getln.c
+
+getln2.o: \
+compile getln2.c substdio.h getln2.c stralloc.h gen_alloc.h \
+stralloc.h getln2.c byte.h getln2.c getln.h getln2.c
+       ./compile getln2.c
+
+getopt.a: \
+makelib subgetopt.o sgetopt.o
+       ./makelib getopt.a subgetopt.o sgetopt.o
+
+gfrom.o: \
+compile gfrom.c str.h gfrom.c gfrom.h gfrom.c
+       ./compile gfrom.c
+
+hasflock.h: \
+tryflock.c compile load
+       ( ( ./compile tryflock.c && ./load tryflock ) >/dev/null \
+       2>&1 \
+       && echo \#define HASFLOCK 1 || exit 0 ) > hasflock.h
+       rm -f tryflock.o tryflock
+
+hasmkffo.h: \
+trymkffo.c compile load
+       ( ( ./compile trymkffo.c && ./load trymkffo ) >/dev/null \
+       2>&1 \
+       && echo \#define HASMKFIFO 1 || exit 0 ) > hasmkffo.h
+       rm -f trymkffo.o trymkffo
+
+hasnpbg1.h: \
+trynpbg1.c compile load open.h open.a fifo.h fifo.o select.h
+       ( ( ./compile trynpbg1.c \
+       && ./load trynpbg1 fifo.o open.a && ./trynpbg1 ) \
+       >/dev/null 2>&1 \
+       && echo \#define HASNAMEDPIPEBUG1 1 || exit 0 ) > \
+       hasnpbg1.h
+       rm -f trynpbg1.o trynpbg1
+
+hassalen.h: \
+trysalen.c compile
+       ( ./compile trysalen.c >/dev/null 2>&1 \
+       && echo \#define HASSALEN 1 || exit 0 ) > hassalen.h
+       rm -f trysalen.o
+
+hassgact.h: \
+trysgact.c compile load
+       ( ( ./compile trysgact.c && ./load trysgact ) >/dev/null \
+       2>&1 \
+       && echo \#define HASSIGACTION 1 || exit 0 ) > hassgact.h
+       rm -f trysgact.o trysgact
+
+hassgprm.h: \
+trysgprm.c compile load
+       ( ( ./compile trysgprm.c && ./load trysgprm ) >/dev/null \
+       2>&1 \
+       && echo \#define HASSIGPROCMASK 1 || exit 0 ) > hassgprm.h
+       rm -f trysgprm.o trysgprm
+
+hasshsgr.h: \
+chkshsgr warn-shsgr tryshsgr.c compile load
+       ./chkshsgr || ( cat warn-shsgr; exit 1 )
+       ( ( ./compile tryshsgr.c \
+       && ./load tryshsgr && ./tryshsgr ) >/dev/null 2>&1 \
+       && echo \#define HASSHORTSETGROUPS 1 || exit 0 ) > \
+       hasshsgr.h
+       rm -f tryshsgr.o tryshsgr
+
+haswaitp.h: \
+trywaitp.c compile load
+       ( ( ./compile trywaitp.c && ./load trywaitp ) >/dev/null \
+       2>&1 \
+       && echo \#define HASWAITPID 1 || exit 0 ) > haswaitp.h
+       rm -f trywaitp.o trywaitp
+
+headerbody.o: \
+compile headerbody.c stralloc.h gen_alloc.h stralloc.h headerbody.c \
+substdio.h headerbody.c getln.h headerbody.c hfield.h headerbody.c \
+headerbody.h headerbody.c
+       ./compile headerbody.c
+
+hfield.o: \
+compile hfield.c hfield.h hfield.c
+       ./compile hfield.c
+
+hostname: \
+load hostname.o substdio.a error.a str.a dns.lib socket.lib
+       ./load hostname substdio.a error.a str.a  `cat dns.lib` \
+       `cat socket.lib`
+
+hostname.o: \
+compile hostname.c substdio.h hostname.c subfd.h substdio.h \
+substdio.h subfd.h hostname.c readwrite.h hostname.c exit.h \
+hostname.c
+       ./compile hostname.c
+
+install: \
+load install.o fifo.o getln.a strerr.a substdio.a stralloc.a alloc.a \
+open.a error.a str.a fs.a
+       ./load install fifo.o getln.a strerr.a substdio.a \
+       stralloc.a alloc.a open.a error.a str.a fs.a 
+
+install.o: \
+compile install.c substdio.h install.c stralloc.h gen_alloc.h \
+stralloc.h install.c getln.h install.c readwrite.h install.c exit.h \
+install.c open.h install.c error.h install.c strerr.h install.c \
+byte.h install.c fifo.h install.c
+       ./compile install.c
+
+instcheck: \
+load instcheck.o getln.a strerr.a substdio.a stralloc.a alloc.a \
+error.a str.a fs.a
+       ./load instcheck getln.a strerr.a substdio.a stralloc.a \
+       alloc.a error.a str.a fs.a 
+
+instcheck.o: \
+compile instcheck.c instcheck.c instcheck.c substdio.h instcheck.c \
+stralloc.h gen_alloc.h stralloc.h instcheck.c getln.h instcheck.c \
+readwrite.h instcheck.c exit.h instcheck.c error.h instcheck.c \
+strerr.h instcheck.c byte.h instcheck.c
+       ./compile instcheck.c
+
+ip.o: \
+compile ip.c fmt.h ip.c scan.h ip.c ip.h ip.c
+       ./compile ip.c
+
+ipalloc.o: \
+compile ipalloc.c alloc.h ipalloc.c gen_allocdefs.h gen_allocdefs.h \
+gen_allocdefs.h ipalloc.c ip.h ipalloc.c ipalloc.h ip.h ip.h \
+ipalloc.h gen_alloc.h ipalloc.h ipalloc.c
+       ./compile ipalloc.c
+
+ipme.o: \
+compile ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c ipme.c \
+hassalen.h ipme.c byte.h ipme.c ip.h ipme.c ipalloc.h ip.h ip.h \
+ipalloc.h gen_alloc.h ipalloc.h ipme.c stralloc.h gen_alloc.h \
+stralloc.h ipme.c ipme.h ip.h ip.h ipme.h ipalloc.h ipalloc.h ipme.h \
+ipme.c ipme.c
+       ./compile ipme.c
+
+ipmeprint: \
+load ipmeprint.o ipme.o ip.o ipalloc.o stralloc.a alloc.a substdio.a \
+error.a str.a fs.a socket.lib
+       ./load ipmeprint ipme.o ip.o ipalloc.o stralloc.a alloc.a \
+       substdio.a error.a str.a fs.a  `cat socket.lib`
+
+ipmeprint.o: \
+compile ipmeprint.c subfd.h substdio.h subfd.h ipmeprint.c substdio.h \
+substdio.h ipmeprint.c ip.h ipmeprint.c ipme.h ip.h ip.h ipme.h \
+ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h ipme.h \
+ipmeprint.c exit.h ipmeprint.c
+       ./compile ipmeprint.c
+
+it: \
+qmail-local qmail-lspawn qmail-getpw qmail-remote qmail-rspawn \
+qmail-clean qmail-send qmail-start splogger qmail-queue qmail-inject \
+predate datemail mailsubj qmail-upq qmail-config qmail-showctl \
+qmail-newu qmail-pw2u qmail-qread qmail-qstat qmail-tcpto qmail-pop3d \
+qmail-popup qmail-qmtpd qmail-smtpd sendmail tcp-env dnscname dnsptr \
+dnsip dnsmxip dnsfq hostname ipmeprint qlist qlist2 qreceipt qsmhook \
+qbiff forward preline condredirect maildirmake maildir2mbox \
+maildirwatch qail elq pinq qmail-hier install instcheck
+
+load: \
+make-load warn-auto.sh systype
+       ( cat warn-auto.sh; ./make-load "`cat systype`" ) > load
+       chmod 755 load
+
+lock.a: \
+makelib lock_ex.o lock_exnb.o lock_un.o
+       ./makelib lock.a lock_ex.o lock_exnb.o lock_un.o
+
+lock_ex.o: \
+compile lock_ex.c lock_ex.c lock_ex.c lock_ex.c hasflock.h lock_ex.c \
+lock.h lock_ex.c
+       ./compile lock_ex.c
+
+lock_exnb.o: \
+compile lock_exnb.c lock_exnb.c lock_exnb.c lock_exnb.c hasflock.h \
+lock_exnb.c lock.h lock_exnb.c
+       ./compile lock_exnb.c
+
+lock_un.o: \
+compile lock_un.c lock_un.c lock_un.c lock_un.c hasflock.h lock_un.c \
+lock.h lock_un.c
+       ./compile lock_un.c
+
+maildir.0: \
+maildir.5
+       nroff -man maildir.5 > maildir.0
+
+maildir.o: \
+compile maildir.c maildir.c maildir.c prioq.h datetime.h prioq.h \
+gen_alloc.h prioq.h maildir.c env.h maildir.c stralloc.h gen_alloc.h \
+stralloc.h maildir.c direntry.h direntry.h direntry.h maildir.c \
+datetime.h datetime.h maildir.c now.h datetime.h datetime.h now.h \
+maildir.c str.h maildir.c maildir.h strerr.h maildir.h maildir.c
+       ./compile maildir.c
+
+maildir2mbox: \
+load maildir2mbox.o maildir.o prioq.o now.o myctime.o gfrom.o lock.a \
+getln.a env.a open.a strerr.a stralloc.a alloc.a substdio.a error.a \
+str.a fs.a datetime.a
+       ./load maildir2mbox maildir.o prioq.o now.o myctime.o \
+       gfrom.o lock.a getln.a env.a open.a strerr.a stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a datetime.a 
+
+maildir2mbox.0: \
+maildir2mbox.1
+       nroff -man maildir2mbox.1 > maildir2mbox.0
+
+maildir2mbox.o: \
+compile maildir2mbox.c readwrite.h maildir2mbox.c prioq.h datetime.h \
+prioq.h gen_alloc.h prioq.h maildir2mbox.c env.h maildir2mbox.c \
+stralloc.h gen_alloc.h stralloc.h maildir2mbox.c subfd.h substdio.h \
+subfd.h maildir2mbox.c substdio.h substdio.h maildir2mbox.c getln.h \
+maildir2mbox.c error.h maildir2mbox.c open.h maildir2mbox.c lock.h \
+maildir2mbox.c gfrom.h maildir2mbox.c str.h maildir2mbox.c exit.h \
+maildir2mbox.c myctime.h maildir2mbox.c maildir.h strerr.h maildir.h \
+maildir2mbox.c
+       ./compile maildir2mbox.c
+
+maildirmake: \
+load maildirmake.o substdio.a error.a str.a
+       ./load maildirmake substdio.a error.a str.a 
+
+maildirmake.0: \
+maildirmake.1
+       nroff -man maildirmake.1 > maildirmake.0
+
+maildirmake.o: \
+compile maildirmake.c subfd.h substdio.h subfd.h maildirmake.c \
+substdio.h substdio.h maildirmake.c error.h maildirmake.c exit.h \
+maildirmake.c
+       ./compile maildirmake.c
+
+maildirwatch: \
+load maildirwatch.o hfield.o headerbody.o maildir.o prioq.o now.o \
+getln.a env.a open.a strerr.a stralloc.a alloc.a substdio.a error.a \
+str.a
+       ./load maildirwatch hfield.o headerbody.o maildir.o \
+       prioq.o now.o getln.a env.a open.a strerr.a stralloc.a \
+       alloc.a substdio.a error.a str.a 
+
+maildirwatch.0: \
+maildirwatch.1
+       nroff -man maildirwatch.1 > maildirwatch.0
+
+maildirwatch.o: \
+compile maildirwatch.c getln.h maildirwatch.c substdio.h \
+maildirwatch.c subfd.h substdio.h substdio.h subfd.h maildirwatch.c \
+prioq.h datetime.h prioq.h gen_alloc.h prioq.h maildirwatch.c \
+stralloc.h gen_alloc.h stralloc.h maildirwatch.c str.h maildirwatch.c \
+exit.h maildirwatch.c hfield.h maildirwatch.c readwrite.h \
+maildirwatch.c open.h maildirwatch.c headerbody.h maildirwatch.c \
+maildir.h strerr.h maildir.h maildirwatch.c
+       ./compile maildirwatch.c
+
+mailsubj: \
+warn-auto.sh mailsubj.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh mailsubj.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > mailsubj
+       chmod 755 mailsubj
+
+mailsubj.0: \
+mailsubj.1
+       nroff -man mailsubj.1 > mailsubj.0
+
+make-compile: \
+make-compile.sh auto-ccld.sh
+       cat auto-ccld.sh make-compile.sh > make-compile
+       chmod 755 make-compile
+
+make-load: \
+make-load.sh auto-ccld.sh
+       cat auto-ccld.sh make-load.sh > make-load
+       chmod 755 make-load
+
+make-makelib: \
+make-makelib.sh auto-ccld.sh
+       cat auto-ccld.sh make-makelib.sh > make-makelib
+       chmod 755 make-makelib
+
+makelib: \
+make-makelib warn-auto.sh systype
+       ( cat warn-auto.sh; ./make-makelib "`cat systype`" ) > \
+       makelib
+       chmod 755 makelib
+
+man: \
+qmail-local.0 qmail-lspawn.0 qmail-getpw.0 qmail-remote.0 \
+qmail-rspawn.0 qmail-clean.0 qmail-send.0 qmail-start.0 splogger.0 \
+qmail-queue.0 qmail-inject.0 mailsubj.0 qmail-showctl.0 qmail-newu.0 \
+qmail-pw2u.0 qmail-qread.0 qmail-qstat.0 qmail-tcpto.0 qmail-pop3d.0 \
+qmail-popup.0 qmail-qmtpd.0 qmail-smtpd.0 tcp-env.0 qlist.0 \
+qreceipt.0 qbiff.0 forward.0 preline.0 condredirect.0 maildirmake.0 \
+maildir2mbox.0 maildirwatch.0 qmail.0 qmail-upgrade.0 qmail-limits.0 \
+qmail-log.0 qmail-control.0 qmail-header.0 qmail-users.0 dot-qmail.0 \
+qmail-command.0 tcp-environ.0 maildir.0 mbox.0 addresses.0 \
+envelopes.0 forgeries.0
+
+mbox.0: \
+mbox.5
+       nroff -man mbox.5 > mbox.0
+
+myctime.o: \
+compile myctime.c datetime.h myctime.c fmt.h myctime.c myctime.h \
+myctime.c
+       ./compile myctime.c
+
+ndelay.a: \
+makelib ndelay.o ndelay_off.o
+       ./makelib ndelay.a ndelay.o ndelay_off.o
+
+ndelay.o: \
+compile ndelay.c ndelay.c ndelay.c ndelay.h ndelay.c
+       ./compile ndelay.c
+
+ndelay_off.o: \
+compile ndelay_off.c ndelay_off.c ndelay_off.c ndelay.h ndelay_off.c
+       ./compile ndelay_off.c
+
+newfield.o: \
+compile newfield.c fmt.h newfield.c datetime.h newfield.c stralloc.h \
+gen_alloc.h stralloc.h newfield.c date822fmt.h newfield.c newfield.h \
+stralloc.h stralloc.h newfield.h newfield.c
+       ./compile newfield.c
+
+now.o: \
+compile now.c now.c datetime.h now.c now.h datetime.h datetime.h \
+now.h now.c
+       ./compile now.c
+
+open.a: \
+makelib open_append.o open_excl.o open_read.o open_trunc.o \
+open_write.o
+       ./makelib open.a open_append.o open_excl.o open_read.o \
+       open_trunc.o open_write.o
+
+open_append.o: \
+compile open_append.c open_append.c open_append.c open.h \
+open_append.c
+       ./compile open_append.c
+
+open_excl.o: \
+compile open_excl.c open_excl.c open_excl.c open.h open_excl.c
+       ./compile open_excl.c
+
+open_read.o: \
+compile open_read.c open_read.c open_read.c open.h open_read.c
+       ./compile open_read.c
+
+open_trunc.o: \
+compile open_trunc.c open_trunc.c open_trunc.c open.h open_trunc.c
+       ./compile open_trunc.c
+
+open_write.o: \
+compile open_write.c open_write.c open_write.c open.h open_write.c
+       ./compile open_write.c
+
+pinq: \
+warn-auto.sh pinq.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh pinq.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > pinq
+       chmod 755 pinq
+
+predate: \
+load predate.o datetime.a sig.a fd.a wait.a substdio.a error.a str.a \
+fs.a
+       ./load predate datetime.a sig.a fd.a wait.a substdio.a \
+       error.a str.a fs.a 
+
+predate.o: \
+compile predate.c predate.c predate.c datetime.h predate.c fork.h \
+predate.c wait.h predate.c fd.h predate.c fmt.h predate.c substdio.h \
+predate.c subfd.h substdio.h substdio.h subfd.h predate.c readwrite.h \
+predate.c exit.h predate.c
+       ./compile predate.c
+
+preline: \
+load preline.o fd.a wait.a sig.a env.a getopt.a substdio.a error.a \
+str.a
+       ./load preline fd.a wait.a sig.a env.a getopt.a substdio.a \
+       error.a str.a 
+
+preline.0: \
+preline.1
+       nroff -man preline.1 > preline.0
+
+preline.o: \
+compile preline.c fd.h preline.c sgetopt.h subgetopt.h sgetopt.h \
+preline.c readwrite.h preline.c subfd.h substdio.h subfd.h preline.c \
+substdio.h substdio.h preline.c exit.h preline.c fork.h preline.c \
+wait.h preline.c env.h preline.c sig.h preline.c error.h preline.c
+       ./compile preline.c
+
+prioq.o: \
+compile prioq.c alloc.h prioq.c gen_allocdefs.h gen_allocdefs.h \
+gen_allocdefs.h prioq.c prioq.h datetime.h prioq.h gen_alloc.h \
+prioq.h prioq.c
+       ./compile prioq.c
+
+prot.o: \
+compile prot.c hasshsgr.h prot.c prot.h prot.c
+       ./compile prot.c
+
+qail: \
+warn-auto.sh qail.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh qail.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > qail
+       chmod 755 qail
+
+qbiff: \
+load qbiff.o headerbody.o hfield.o getln.a env.a open.a stralloc.a \
+alloc.a substdio.a error.a str.a
+       ./load qbiff headerbody.o hfield.o getln.a env.a open.a \
+       stralloc.a alloc.a substdio.a error.a str.a 
+
+qbiff.0: \
+qbiff.1
+       nroff -man qbiff.1 > qbiff.0
+
+qbiff.o: \
+compile qbiff.c qbiff.c qbiff.c qbiff.c readwrite.h qbiff.c \
+stralloc.h gen_alloc.h stralloc.h qbiff.c substdio.h qbiff.c subfd.h \
+substdio.h substdio.h subfd.h qbiff.c open.h qbiff.c byte.h qbiff.c \
+str.h qbiff.c headerbody.h qbiff.c hfield.h qbiff.c env.h qbiff.c \
+exit.h qbiff.c
+       ./compile qbiff.c
+
+qlist: \
+load qlist.o headerbody.o hfield.o token822.o qmail.o getln.a env.a \
+case.a sig.a fd.a wait.a open.a lock.a stralloc.a alloc.a substdio.a \
+error.a str.a auto_qmail.o
+       ./load qlist headerbody.o hfield.o token822.o qmail.o \
+       getln.a env.a case.a sig.a fd.a wait.a open.a lock.a \
+       stralloc.a alloc.a substdio.a error.a str.a auto_qmail.o 
+
+qlist.0: \
+qlist.1
+       nroff -man qlist.1 > qlist.0
+
+qlist.o: \
+compile qlist.c sig.h qlist.c readwrite.h qlist.c substdio.h qlist.c \
+stralloc.h gen_alloc.h stralloc.h qlist.c subfd.h substdio.h \
+substdio.h subfd.h qlist.c getln.h qlist.c alloc.h qlist.c str.h \
+qlist.c env.h qlist.c hfield.h qlist.c case.h qlist.c token822.h \
+gen_alloc.h token822.h qlist.c error.h qlist.c gen_alloc.h qlist.c \
+gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qlist.c headerbody.h \
+qlist.c exit.h qlist.c open.h qlist.c lock.h qlist.c qmail.h \
+substdio.h substdio.h qmail.h qlist.c qlist.c
+       ./compile qlist.c
+
+qlist2: \
+warn-auto.sh qlist2.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh qlist2.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > qlist2
+       chmod 755 qlist2
+
+qmail-clean: \
+load qmail-clean.o fmtqfn.o now.o getln.a sig.a stralloc.a alloc.a \
+substdio.a error.a str.a fs.a auto_qmail.o auto_split.o
+       ./load qmail-clean fmtqfn.o now.o getln.a sig.a stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a auto_qmail.o \
+       auto_split.o 
+
+qmail-clean.0: \
+qmail-clean.8
+       nroff -man qmail-clean.8 > qmail-clean.0
+
+qmail-clean.o: \
+compile qmail-clean.c qmail-clean.c qmail-clean.c readwrite.h \
+qmail-clean.c sig.h qmail-clean.c now.h datetime.h now.h \
+qmail-clean.c str.h qmail-clean.c direntry.h direntry.h direntry.h \
+qmail-clean.c getln.h qmail-clean.c stralloc.h gen_alloc.h stralloc.h \
+qmail-clean.c substdio.h qmail-clean.c subfd.h substdio.h substdio.h \
+subfd.h qmail-clean.c byte.h qmail-clean.c scan.h qmail-clean.c fmt.h \
+qmail-clean.c error.h qmail-clean.c exit.h qmail-clean.c fmtqfn.h \
+qmail-clean.c auto_qmail.h qmail-clean.c
+       ./compile qmail-clean.c
+
+qmail-command.0: \
+qmail-command.8
+       nroff -man qmail-command.8 > qmail-command.0
+
+qmail-config: \
+warn-auto.sh qmail-config.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh qmail-config.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > qmail-config
+       chmod 755 qmail-config
+
+qmail-control.0: \
+qmail-control.5
+       nroff -man qmail-control.5 > qmail-control.0
+
+qmail-control.5: \
+qmail-control.9 conf-break conf-spawn
+       cat qmail-control.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-control.5
+
+qmail-getpw: \
+load qmail-getpw.o case.a substdio.a error.a str.a fs.a auto_break.o \
+auto_usera.o
+       ./load qmail-getpw case.a substdio.a error.a str.a fs.a \
+       auto_break.o auto_usera.o 
+
+qmail-getpw.0: \
+qmail-getpw.8
+       nroff -man qmail-getpw.8 > qmail-getpw.0
+
+qmail-getpw.8: \
+qmail-getpw.9 conf-break conf-spawn
+       cat qmail-getpw.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-getpw.8
+
+qmail-getpw.o: \
+compile qmail-getpw.c qmail-getpw.c qmail-getpw.c qmail-getpw.c \
+readwrite.h qmail-getpw.c substdio.h qmail-getpw.c subfd.h substdio.h \
+substdio.h subfd.h qmail-getpw.c error.h qmail-getpw.c exit.h \
+qmail-getpw.c byte.h qmail-getpw.c str.h qmail-getpw.c case.h \
+qmail-getpw.c fmt.h qmail-getpw.c auto_usera.h qmail-getpw.c \
+auto_break.h qmail-getpw.c qlx.h qmail-getpw.c
+       ./compile qmail-getpw.c
+
+qmail-header.0: \
+qmail-header.5
+       nroff -man qmail-header.5 > qmail-header.0
+
+qmail-hier: \
+load qmail-hier.o substdio.a error.a str.a fs.a auto_split.o \
+auto_uids.o
+       ./load qmail-hier substdio.a error.a str.a fs.a \
+       auto_split.o auto_uids.o 
+
+qmail-hier.o: \
+compile qmail-hier.c subfd.h substdio.h subfd.h qmail-hier.c \
+substdio.h substdio.h qmail-hier.c auto_split.h qmail-hier.c \
+auto_uids.h qmail-hier.c fmt.h qmail-hier.c
+       ./compile qmail-hier.c
+
+qmail-inject: \
+load qmail-inject.o headerbody.o hfield.o newfield.o quote.o now.o \
+control.o date822fmt.o qmail.o fd.a wait.a open.a getln.a sig.a \
+getopt.a datetime.a token822.o env.a stralloc.a alloc.a substdio.a \
+error.a str.a fs.a auto_qmail.o
+       ./load qmail-inject headerbody.o hfield.o newfield.o \
+       quote.o now.o control.o date822fmt.o qmail.o fd.a wait.a \
+       open.a getln.a sig.a getopt.a datetime.a token822.o env.a \
+       stralloc.a alloc.a substdio.a error.a str.a fs.a \
+       auto_qmail.o 
+
+qmail-inject.0: \
+qmail-inject.8
+       nroff -man qmail-inject.8 > qmail-inject.0
+
+qmail-inject.o: \
+compile qmail-inject.c sig.h qmail-inject.c substdio.h qmail-inject.c \
+stralloc.h gen_alloc.h stralloc.h qmail-inject.c subfd.h substdio.h \
+substdio.h subfd.h qmail-inject.c sgetopt.h subgetopt.h sgetopt.h \
+qmail-inject.c getln.h qmail-inject.c alloc.h qmail-inject.c str.h \
+qmail-inject.c fmt.h qmail-inject.c hfield.h qmail-inject.c \
+token822.h gen_alloc.h token822.h qmail-inject.c control.h \
+qmail-inject.c env.h qmail-inject.c gen_alloc.h qmail-inject.c \
+gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qmail-inject.c \
+error.h qmail-inject.c qmail.h substdio.h substdio.h qmail.h \
+qmail-inject.c now.h datetime.h now.h qmail-inject.c exit.h \
+qmail-inject.c quote.h qmail-inject.c headerbody.h qmail-inject.c \
+auto_qmail.h qmail-inject.c newfield.h stralloc.h stralloc.h \
+newfield.h qmail-inject.c
+       ./compile qmail-inject.c
+
+qmail-limits.0: \
+qmail-limits.7
+       nroff -man qmail-limits.7 > qmail-limits.0
+
+qmail-limits.7: \
+qmail-limits.9 conf-break conf-spawn
+       cat qmail-limits.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-limits.7
+
+qmail-local: \
+load qmail-local.o qmail.o quote.o now.o gfrom.o myctime.o \
+slurpclose.o case.a getln.a getopt.a sig.a open.a seek.a lock.a fd.a \
+wait.a env.a stralloc.a alloc.a substdio.a error.a str.a fs.a \
+datetime.a auto_qmail.o auto_patrn.o socket.lib
+       ./load qmail-local qmail.o quote.o now.o gfrom.o myctime.o \
+       slurpclose.o case.a getln.a getopt.a sig.a open.a seek.a \
+       lock.a fd.a wait.a env.a stralloc.a alloc.a substdio.a \
+       error.a str.a fs.a datetime.a auto_qmail.o auto_patrn.o  \
+       `cat socket.lib`
+
+qmail-local.0: \
+qmail-local.8
+       nroff -man qmail-local.8 > qmail-local.0
+
+qmail-local.o: \
+compile qmail-local.c qmail-local.c qmail-local.c readwrite.h \
+qmail-local.c sig.h qmail-local.c env.h qmail-local.c byte.h \
+qmail-local.c exit.h qmail-local.c fork.h qmail-local.c open.h \
+qmail-local.c wait.h qmail-local.c lock.h qmail-local.c seek.h \
+qmail-local.c substdio.h qmail-local.c getln.h qmail-local.c subfd.h \
+substdio.h substdio.h subfd.h qmail-local.c sgetopt.h subgetopt.h \
+sgetopt.h qmail-local.c alloc.h qmail-local.c error.h qmail-local.c \
+stralloc.h gen_alloc.h stralloc.h qmail-local.c fmt.h qmail-local.c \
+str.h qmail-local.c now.h datetime.h now.h qmail-local.c case.h \
+qmail-local.c quote.h qmail-local.c qmail.h substdio.h substdio.h \
+qmail.h qmail-local.c slurpclose.h qmail-local.c myctime.h \
+qmail-local.c gfrom.h qmail-local.c auto_patrn.h qmail-local.c
+       ./compile qmail-local.c
+
+qmail-log.0: \
+qmail-log.5
+       nroff -man qmail-log.5 > qmail-log.0
+
+qmail-lspawn: \
+load qmail-lspawn.o spawn.o prot.o slurpclose.o coe.o sig.a wait.a \
+case.a cdb.a fd.a open.a stralloc.a alloc.a substdio.a error.a str.a \
+fs.a auto_qmail.o auto_uids.o auto_spawn.o
+       ./load qmail-lspawn spawn.o prot.o slurpclose.o coe.o \
+       sig.a wait.a case.a cdb.a fd.a open.a stralloc.a alloc.a \
+       substdio.a error.a str.a fs.a auto_qmail.o auto_uids.o \
+       auto_spawn.o 
+
+qmail-lspawn.0: \
+qmail-lspawn.8
+       nroff -man qmail-lspawn.8 > qmail-lspawn.0
+
+qmail-lspawn.o: \
+compile qmail-lspawn.c fd.h qmail-lspawn.c wait.h qmail-lspawn.c \
+prot.h qmail-lspawn.c substdio.h qmail-lspawn.c stralloc.h \
+gen_alloc.h stralloc.h qmail-lspawn.c scan.h qmail-lspawn.c exit.h \
+qmail-lspawn.c fork.h qmail-lspawn.c error.h qmail-lspawn.c cdb.h \
+uint32.h cdb.h qmail-lspawn.c case.h qmail-lspawn.c slurpclose.h \
+qmail-lspawn.c auto_qmail.h qmail-lspawn.c auto_uids.h qmail-lspawn.c \
+qlx.h qmail-lspawn.c
+       ./compile qmail-lspawn.c
+
+qmail-newu: \
+load qmail-newu.o cdbmss.o getln.a open.a seek.a cdbmake.a case.a \
+stralloc.a alloc.a substdio.a error.a str.a auto_qmail.o
+       ./load qmail-newu cdbmss.o getln.a open.a seek.a cdbmake.a \
+       case.a stralloc.a alloc.a substdio.a error.a str.a \
+       auto_qmail.o 
+
+qmail-newu.0: \
+qmail-newu.8
+       nroff -man qmail-newu.8 > qmail-newu.0
+
+qmail-newu.8: \
+qmail-newu.9 conf-break conf-spawn
+       cat qmail-newu.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-newu.8
+
+qmail-newu.o: \
+compile qmail-newu.c stralloc.h gen_alloc.h stralloc.h qmail-newu.c \
+subfd.h substdio.h subfd.h qmail-newu.c getln.h qmail-newu.c \
+substdio.h substdio.h qmail-newu.c cdbmss.h cdbmake.h uint32.h \
+cdbmake.h cdbmss.h substdio.h substdio.h cdbmss.h qmail-newu.c exit.h \
+qmail-newu.c readwrite.h qmail-newu.c open.h qmail-newu.c error.h \
+qmail-newu.c case.h qmail-newu.c auto_qmail.h qmail-newu.c
+       ./compile qmail-newu.c
+
+qmail-pop3d: \
+load qmail-pop3d.o prioq.o now.o sig.a open.a getln.a stralloc.a \
+alloc.a substdio.a error.a str.a fs.a
+       ./load qmail-pop3d prioq.o now.o sig.a open.a getln.a \
+       stralloc.a alloc.a substdio.a error.a str.a fs.a 
+
+qmail-pop3d.0: \
+qmail-pop3d.8
+       nroff -man qmail-pop3d.8 > qmail-pop3d.0
+
+qmail-pop3d.o: \
+compile qmail-pop3d.c qmail-pop3d.c qmail-pop3d.c direntry.h \
+direntry.h direntry.h qmail-pop3d.c sig.h qmail-pop3d.c getln.h \
+qmail-pop3d.c stralloc.h gen_alloc.h stralloc.h qmail-pop3d.c \
+substdio.h qmail-pop3d.c alloc.h qmail-pop3d.c datetime.h \
+qmail-pop3d.c prot.h qmail-pop3d.c open.h qmail-pop3d.c prioq.h \
+datetime.h datetime.h prioq.h gen_alloc.h prioq.h qmail-pop3d.c \
+scan.h qmail-pop3d.c fmt.h qmail-pop3d.c error.h qmail-pop3d.c str.h \
+qmail-pop3d.c exit.h qmail-pop3d.c now.h datetime.h datetime.h now.h \
+qmail-pop3d.c readwrite.h qmail-pop3d.c
+       ./compile qmail-pop3d.c
+
+qmail-popup: \
+load qmail-popup.o now.o fd.a sig.a wait.a getln.a stralloc.a alloc.a \
+substdio.a error.a str.a fs.a
+       ./load qmail-popup now.o fd.a sig.a wait.a getln.a \
+       stralloc.a alloc.a substdio.a error.a str.a fs.a 
+
+qmail-popup.0: \
+qmail-popup.8
+       nroff -man qmail-popup.8 > qmail-popup.0
+
+qmail-popup.o: \
+compile qmail-popup.c qmail-popup.c qmail-popup.c fd.h qmail-popup.c \
+sig.h qmail-popup.c getln.h qmail-popup.c stralloc.h gen_alloc.h \
+stralloc.h qmail-popup.c substdio.h qmail-popup.c subfd.h substdio.h \
+substdio.h subfd.h qmail-popup.c alloc.h qmail-popup.c datetime.h \
+qmail-popup.c error.h qmail-popup.c wait.h qmail-popup.c str.h \
+qmail-popup.c now.h datetime.h datetime.h now.h qmail-popup.c fmt.h \
+qmail-popup.c exit.h qmail-popup.c readwrite.h qmail-popup.c
+       ./compile qmail-popup.c
+
+qmail-pw2u: \
+load qmail-pw2u.o constmap.o control.o open.a getln.a case.a getopt.a \
+stralloc.a alloc.a substdio.a error.a str.a fs.a auto_usera.o \
+auto_break.o auto_qmail.o
+       ./load qmail-pw2u constmap.o control.o open.a getln.a \
+       case.a getopt.a stralloc.a alloc.a substdio.a error.a str.a \
+       fs.a auto_usera.o auto_break.o auto_qmail.o 
+
+qmail-pw2u.0: \
+qmail-pw2u.8
+       nroff -man qmail-pw2u.8 > qmail-pw2u.0
+
+qmail-pw2u.8: \
+qmail-pw2u.9 conf-break conf-spawn
+       cat qmail-pw2u.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-pw2u.8
+
+qmail-pw2u.o: \
+compile qmail-pw2u.c qmail-pw2u.c qmail-pw2u.c substdio.h \
+qmail-pw2u.c readwrite.h qmail-pw2u.c subfd.h substdio.h substdio.h \
+subfd.h qmail-pw2u.c sgetopt.h subgetopt.h sgetopt.h qmail-pw2u.c \
+control.h qmail-pw2u.c constmap.h qmail-pw2u.c stralloc.h gen_alloc.h \
+stralloc.h qmail-pw2u.c fmt.h qmail-pw2u.c str.h qmail-pw2u.c scan.h \
+qmail-pw2u.c open.h qmail-pw2u.c error.h qmail-pw2u.c getln.h \
+qmail-pw2u.c auto_break.h qmail-pw2u.c auto_qmail.h qmail-pw2u.c \
+auto_usera.h qmail-pw2u.c
+       ./compile qmail-pw2u.c
+
+qmail-qmtpd: \
+load qmail-qmtpd.o control.o constmap.o received.o date822fmt.o now.o \
+qmail.o fd.a wait.a datetime.a open.a getln.a sig.a case.a env.a \
+stralloc.a alloc.a substdio.a error.a str.a fs.a auto_qmail.o
+       ./load qmail-qmtpd control.o constmap.o received.o \
+       date822fmt.o now.o qmail.o fd.a wait.a datetime.a open.a \
+       getln.a sig.a case.a env.a stralloc.a alloc.a substdio.a \
+       error.a str.a fs.a auto_qmail.o 
+
+qmail-qmtpd.0: \
+qmail-qmtpd.8
+       nroff -man qmail-qmtpd.8 > qmail-qmtpd.0
+
+qmail-qmtpd.o: \
+compile qmail-qmtpd.c stralloc.h gen_alloc.h stralloc.h qmail-qmtpd.c \
+substdio.h qmail-qmtpd.c subfd.h substdio.h substdio.h subfd.h \
+qmail-qmtpd.c qmail.h substdio.h substdio.h qmail.h qmail-qmtpd.c \
+now.h datetime.h now.h qmail-qmtpd.c str.h qmail-qmtpd.c fmt.h \
+qmail-qmtpd.c env.h qmail-qmtpd.c sig.h qmail-qmtpd.c auto_qmail.h \
+qmail-qmtpd.c now.h qmail-qmtpd.c datetime.h datetime.h qmail-qmtpd.c \
+date822fmt.h qmail-qmtpd.c readwrite.h qmail-qmtpd.c control.h \
+qmail-qmtpd.c constmap.h qmail-qmtpd.c received.h qmail-qmtpd.c
+       ./compile qmail-qmtpd.c
+
+qmail-qread: \
+load qmail-qread.o fmtqfn.o readsubdir.o date822fmt.o datetime.a \
+open.a getln.a stralloc.a alloc.a substdio.a error.a str.a fs.a \
+auto_qmail.o auto_split.o
+       ./load qmail-qread fmtqfn.o readsubdir.o date822fmt.o \
+       datetime.a open.a getln.a stralloc.a alloc.a substdio.a \
+       error.a str.a fs.a auto_qmail.o auto_split.o 
+
+qmail-qread.0: \
+qmail-qread.8
+       nroff -man qmail-qread.8 > qmail-qread.0
+
+qmail-qread.o: \
+compile qmail-qread.c qmail-qread.c qmail-qread.c stralloc.h \
+gen_alloc.h stralloc.h qmail-qread.c substdio.h qmail-qread.c subfd.h \
+substdio.h substdio.h subfd.h qmail-qread.c fmt.h qmail-qread.c str.h \
+qmail-qread.c getln.h qmail-qread.c fmtqfn.h qmail-qread.c \
+readsubdir.h direntry.h direntry.h direntry.h readsubdir.h \
+qmail-qread.c auto_qmail.h qmail-qread.c open.h qmail-qread.c \
+datetime.h qmail-qread.c date822fmt.h qmail-qread.c readwrite.h \
+qmail-qread.c error.h qmail-qread.c exit.h qmail-qread.c
+       ./compile qmail-qread.c
+
+qmail-qstat: \
+warn-auto.sh qmail-qstat.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh qmail-qstat.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > qmail-qstat
+       chmod 755 qmail-qstat
+
+qmail-qstat.0: \
+qmail-qstat.8
+       nroff -man qmail-qstat.8 > qmail-qstat.0
+
+qmail-queue: \
+load qmail-queue.o triggerpull.o fmtqfn.o now.o date822fmt.o \
+datetime.a seek.a ndelay.a open.a sig.a alloc.a substdio.a error.a \
+str.a fs.a auto_qmail.o auto_split.o auto_uids.o
+       ./load qmail-queue triggerpull.o fmtqfn.o now.o \
+       date822fmt.o datetime.a seek.a ndelay.a open.a sig.a \
+       alloc.a substdio.a error.a str.a fs.a auto_qmail.o \
+       auto_split.o auto_uids.o 
+
+qmail-queue.0: \
+qmail-queue.8
+       nroff -man qmail-queue.8 > qmail-queue.0
+
+qmail-queue.o: \
+compile qmail-queue.c qmail-queue.c qmail-queue.c readwrite.h \
+qmail-queue.c sig.h qmail-queue.c exit.h qmail-queue.c open.h \
+qmail-queue.c seek.h qmail-queue.c fmt.h qmail-queue.c alloc.h \
+qmail-queue.c substdio.h qmail-queue.c datetime.h qmail-queue.c now.h \
+datetime.h datetime.h now.h qmail-queue.c triggerpull.h qmail-queue.c \
+extra.h qmail-queue.c auto_qmail.h qmail-queue.c auto_uids.h \
+qmail-queue.c date822fmt.h qmail-queue.c fmtqfn.h qmail-queue.c
+       ./compile qmail-queue.c
+
+qmail-remote: \
+load qmail-remote.o control.o constmap.o timeoutread.o timeoutwrite.o \
+timeoutconn.o tcpto.o now.o dns.o ip.o ipalloc.o ipme.o quote.o \
+ndelay.a case.a sig.a open.a lock.a seek.a getln.a stralloc.a alloc.a \
+substdio.a error.a str.a fs.a auto_qmail.o dns.lib socket.lib
+       ./load qmail-remote control.o constmap.o timeoutread.o \
+       timeoutwrite.o timeoutconn.o tcpto.o now.o dns.o ip.o \
+       ipalloc.o ipme.o quote.o ndelay.a case.a sig.a open.a \
+       lock.a seek.a getln.a stralloc.a alloc.a substdio.a error.a \
+       str.a fs.a auto_qmail.o  `cat dns.lib` `cat socket.lib`
+
+qmail-remote.0: \
+qmail-remote.8
+       nroff -man qmail-remote.8 > qmail-remote.0
+
+qmail-remote.o: \
+compile qmail-remote.c qmail-remote.c qmail-remote.c qmail-remote.c \
+qmail-remote.c sig.h qmail-remote.c getln.h qmail-remote.c stralloc.h \
+gen_alloc.h stralloc.h qmail-remote.c substdio.h qmail-remote.c \
+subfd.h substdio.h substdio.h subfd.h qmail-remote.c scan.h \
+qmail-remote.c case.h qmail-remote.c error.h qmail-remote.c \
+auto_qmail.h qmail-remote.c control.h qmail-remote.c dns.h \
+qmail-remote.c alloc.h qmail-remote.c quote.h qmail-remote.c ip.h \
+qmail-remote.c ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h \
+qmail-remote.c ipme.h ip.h ip.h ipme.h ipalloc.h ipalloc.h ipme.h \
+qmail-remote.c gen_alloc.h qmail-remote.c gen_allocdefs.h \
+gen_allocdefs.h gen_allocdefs.h qmail-remote.c str.h qmail-remote.c \
+now.h datetime.h now.h qmail-remote.c exit.h qmail-remote.c \
+constmap.h qmail-remote.c tcpto.h qmail-remote.c timeoutconn.h \
+qmail-remote.c timeoutread.h qmail-remote.c timeoutwrite.h \
+qmail-remote.c
+       ./compile qmail-remote.c
+
+qmail-rspawn: \
+load qmail-rspawn.o spawn.o tcpto_clean.o now.o coe.o sig.a open.a \
+seek.a lock.a wait.a fd.a stralloc.a alloc.a substdio.a error.a str.a \
+auto_qmail.o auto_uids.o auto_spawn.o
+       ./load qmail-rspawn spawn.o tcpto_clean.o now.o coe.o \
+       sig.a open.a seek.a lock.a wait.a fd.a stralloc.a alloc.a \
+       substdio.a error.a str.a auto_qmail.o auto_uids.o \
+       auto_spawn.o 
+
+qmail-rspawn.0: \
+qmail-rspawn.8
+       nroff -man qmail-rspawn.8 > qmail-rspawn.0
+
+qmail-rspawn.o: \
+compile qmail-rspawn.c fd.h qmail-rspawn.c wait.h qmail-rspawn.c \
+substdio.h qmail-rspawn.c exit.h qmail-rspawn.c fork.h qmail-rspawn.c \
+error.h qmail-rspawn.c tcpto.h qmail-rspawn.c
+       ./compile qmail-rspawn.c
+
+qmail-send: \
+load qmail-send.o qsutil.o control.o constmap.o newfield.o prioq.o \
+trigger.o fmtqfn.o quote.o now.o readsubdir.o qmail.o date822fmt.o \
+datetime.a case.a ndelay.a getln.a wait.a seek.a fd.a sig.a open.a \
+lock.a stralloc.a alloc.a substdio.a error.a str.a fs.a auto_qmail.o \
+auto_split.o
+       ./load qmail-send qsutil.o control.o constmap.o newfield.o \
+       prioq.o trigger.o fmtqfn.o quote.o now.o readsubdir.o \
+       qmail.o date822fmt.o datetime.a case.a ndelay.a getln.a \
+       wait.a seek.a fd.a sig.a open.a lock.a stralloc.a alloc.a \
+       substdio.a error.a str.a fs.a auto_qmail.o auto_split.o 
+
+qmail-send.0: \
+qmail-send.8
+       nroff -man qmail-send.8 > qmail-send.0
+
+qmail-send.8: \
+qmail-send.9 conf-break conf-spawn
+       cat qmail-send.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-send.8
+
+qmail-send.o: \
+compile qmail-send.c qmail-send.c qmail-send.c readwrite.h \
+qmail-send.c sig.h qmail-send.c direntry.h direntry.h direntry.h \
+qmail-send.c control.h qmail-send.c select.h select.h select.h \
+select.h qmail-send.c open.h qmail-send.c seek.h qmail-send.c exit.h \
+qmail-send.c lock.h qmail-send.c ndelay.h qmail-send.c now.h \
+datetime.h now.h qmail-send.c getln.h qmail-send.c substdio.h \
+qmail-send.c alloc.h qmail-send.c error.h qmail-send.c stralloc.h \
+gen_alloc.h stralloc.h qmail-send.c str.h qmail-send.c byte.h \
+qmail-send.c fmt.h qmail-send.c scan.h qmail-send.c case.h \
+qmail-send.c auto_qmail.h qmail-send.c trigger.h qmail-send.c \
+newfield.h stralloc.h stralloc.h newfield.h qmail-send.c quote.h \
+qmail-send.c qmail.h substdio.h substdio.h qmail.h qmail-send.c \
+qsutil.h qmail-send.c prioq.h datetime.h datetime.h prioq.h \
+gen_alloc.h prioq.h qmail-send.c constmap.h qmail-send.c fmtqfn.h \
+qmail-send.c readsubdir.h direntry.h readsubdir.h qmail-send.c
+       ./compile qmail-send.c
+
+qmail-showctl: \
+load qmail-showctl.o control.o open.a getln.a stralloc.a alloc.a \
+substdio.a error.a str.a fs.a auto_qmail.o
+       ./load qmail-showctl control.o open.a getln.a stralloc.a \
+       alloc.a substdio.a error.a str.a fs.a auto_qmail.o 
+
+qmail-showctl.0: \
+qmail-showctl.8
+       nroff -man qmail-showctl.8 > qmail-showctl.0
+
+qmail-showctl.o: \
+compile qmail-showctl.c substdio.h qmail-showctl.c subfd.h substdio.h \
+substdio.h subfd.h qmail-showctl.c exit.h qmail-showctl.c fmt.h \
+qmail-showctl.c str.h qmail-showctl.c control.h qmail-showctl.c \
+constmap.h qmail-showctl.c stralloc.h gen_alloc.h stralloc.h \
+qmail-showctl.c direntry.h direntry.h direntry.h qmail-showctl.c \
+auto_qmail.h qmail-showctl.c
+       ./compile qmail-showctl.c
+
+qmail-smtpd: \
+load qmail-smtpd.o ip.o ipme.o ipalloc.o control.o constmap.o \
+received.o date822fmt.o now.o qmail.o fd.a wait.a datetime.a open.a \
+getln.a sig.a case.a env.a stralloc.a alloc.a substdio.a error.a \
+str.a fs.a auto_qmail.o socket.lib
+       ./load qmail-smtpd ip.o ipme.o ipalloc.o control.o \
+       constmap.o received.o date822fmt.o now.o qmail.o fd.a \
+       wait.a datetime.a open.a getln.a sig.a case.a env.a \
+       stralloc.a alloc.a substdio.a error.a str.a fs.a \
+       auto_qmail.o  `cat socket.lib`
+
+qmail-smtpd.0: \
+qmail-smtpd.8
+       nroff -man qmail-smtpd.8 > qmail-smtpd.0
+
+qmail-smtpd.o: \
+compile qmail-smtpd.c sig.h qmail-smtpd.c readwrite.h qmail-smtpd.c \
+getln.h qmail-smtpd.c stralloc.h gen_alloc.h stralloc.h qmail-smtpd.c \
+substdio.h qmail-smtpd.c alloc.h qmail-smtpd.c auto_qmail.h \
+qmail-smtpd.c control.h qmail-smtpd.c received.h qmail-smtpd.c \
+constmap.h qmail-smtpd.c error.h qmail-smtpd.c ipme.h ip.h ipme.h \
+ipalloc.h ip.h ip.h ipalloc.h gen_alloc.h ipalloc.h ipme.h \
+qmail-smtpd.c ip.h ip.h qmail-smtpd.c qmail.h substdio.h substdio.h \
+qmail.h qmail-smtpd.c str.h qmail-smtpd.c fmt.h qmail-smtpd.c byte.h \
+qmail-smtpd.c case.h qmail-smtpd.c env.h qmail-smtpd.c now.h \
+datetime.h now.h qmail-smtpd.c exit.h qmail-smtpd.c
+       ./compile qmail-smtpd.c
+
+qmail-start: \
+load qmail-start.o prot.o fd.a auto_uids.o
+       ./load qmail-start prot.o fd.a auto_uids.o 
+
+qmail-start.0: \
+qmail-start.8
+       nroff -man qmail-start.8 > qmail-start.0
+
+qmail-start.8: \
+qmail-start.9 conf-break conf-spawn
+       cat qmail-start.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-start.8
+
+qmail-start.o: \
+compile qmail-start.c fd.h qmail-start.c prot.h qmail-start.c exit.h \
+qmail-start.c fork.h qmail-start.c auto_uids.h qmail-start.c
+       ./compile qmail-start.c
+
+qmail-tcpto: \
+load qmail-tcpto.o ip.o now.o open.a lock.a substdio.a error.a str.a \
+fs.a auto_qmail.o
+       ./load qmail-tcpto ip.o now.o open.a lock.a substdio.a \
+       error.a str.a fs.a auto_qmail.o 
+
+qmail-tcpto.0: \
+qmail-tcpto.8
+       nroff -man qmail-tcpto.8 > qmail-tcpto.0
+
+qmail-tcpto.o: \
+compile qmail-tcpto.c substdio.h qmail-tcpto.c subfd.h substdio.h \
+substdio.h subfd.h qmail-tcpto.c auto_qmail.h qmail-tcpto.c fmt.h \
+qmail-tcpto.c ip.h qmail-tcpto.c lock.h qmail-tcpto.c error.h \
+qmail-tcpto.c exit.h qmail-tcpto.c datetime.h qmail-tcpto.c now.h \
+datetime.h datetime.h now.h qmail-tcpto.c
+       ./compile qmail-tcpto.c
+
+qmail-upgrade.0: \
+qmail-upgrade.7
+       nroff -man qmail-upgrade.7 > qmail-upgrade.0
+
+qmail-upgrade.7: \
+qmail-upgrade.9 conf-break conf-spawn
+       cat qmail-upgrade.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-upgrade.7
+
+qmail-upq: \
+warn-auto.sh qmail-upq.sh conf-qmail conf-break conf-split
+       cat warn-auto.sh qmail-upq.sh \
+       | sed s}QMAIL}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPLIT}"`head -1 conf-split`"}g \
+       > qmail-upq
+       chmod 755 qmail-upq
+
+qmail-users.0: \
+qmail-users.5
+       nroff -man qmail-users.5 > qmail-users.0
+
+qmail-users.5: \
+qmail-users.9 conf-break conf-spawn
+       cat qmail-users.9 \
+       | sed s}QMAILHOME}"`head -1 conf-qmail`"}g \
+       | sed s}BREAK}"`head -1 conf-break`"}g \
+       | sed s}SPAWN}"`head -1 conf-spawn`"}g \
+       > qmail-users.5
+
+qmail.0: \
+qmail.7
+       nroff -man qmail.7 > qmail.0
+
+qmail.o: \
+compile qmail.c substdio.h qmail.c readwrite.h qmail.c wait.h qmail.c \
+exit.h qmail.c fork.h qmail.c fd.h qmail.c qmail.h substdio.h \
+substdio.h qmail.h qmail.c auto_qmail.h qmail.c
+       ./compile qmail.c
+
+qreceipt: \
+load qreceipt.o headerbody.o hfield.o quote.o token822.o qmail.o \
+getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a substdio.a error.a \
+str.a auto_qmail.o
+       ./load qreceipt headerbody.o hfield.o quote.o token822.o \
+       qmail.o getln.a fd.a wait.a sig.a env.a stralloc.a alloc.a \
+       substdio.a error.a str.a auto_qmail.o 
+
+qreceipt.0: \
+qreceipt.1
+       nroff -man qreceipt.1 > qreceipt.0
+
+qreceipt.o: \
+compile qreceipt.c sig.h qreceipt.c env.h qreceipt.c substdio.h \
+qreceipt.c stralloc.h gen_alloc.h stralloc.h qreceipt.c subfd.h \
+substdio.h substdio.h subfd.h qreceipt.c getln.h qreceipt.c alloc.h \
+qreceipt.c str.h qreceipt.c hfield.h qreceipt.c token822.h \
+gen_alloc.h token822.h qreceipt.c error.h qreceipt.c gen_alloc.h \
+qreceipt.c gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h qreceipt.c \
+headerbody.h qreceipt.c exit.h qreceipt.c open.h qreceipt.c quote.h \
+qreceipt.c qmail.h substdio.h substdio.h qmail.h qreceipt.c
+       ./compile qreceipt.c
+
+qsmhook: \
+load qsmhook.o sig.a case.a fd.a wait.a getopt.a env.a stralloc.a \
+alloc.a substdio.a error.a str.a
+       ./load qsmhook sig.a case.a fd.a wait.a getopt.a env.a \
+       stralloc.a alloc.a substdio.a error.a str.a 
+
+qsmhook.o: \
+compile qsmhook.c fd.h qsmhook.c stralloc.h gen_alloc.h stralloc.h \
+qsmhook.c readwrite.h qsmhook.c sgetopt.h subgetopt.h sgetopt.h \
+qsmhook.c wait.h qsmhook.c env.h qsmhook.c byte.h qsmhook.c str.h \
+qsmhook.c alloc.h qsmhook.c exit.h qsmhook.c fork.h qsmhook.c case.h \
+qsmhook.c subfd.h substdio.h subfd.h qsmhook.c error.h qsmhook.c \
+substdio.h substdio.h qsmhook.c sig.h qsmhook.c
+       ./compile qsmhook.c
+
+qsutil.o: \
+compile qsutil.c stralloc.h gen_alloc.h stralloc.h qsutil.c \
+readwrite.h qsutil.c substdio.h qsutil.c qsutil.h qsutil.c
+       ./compile qsutil.c
+
+quote.o: \
+compile quote.c stralloc.h gen_alloc.h stralloc.h quote.c str.h \
+quote.c quote.h quote.c
+       ./compile quote.c
+
+readsubdir.o: \
+compile readsubdir.c readsubdir.h direntry.h direntry.h direntry.h \
+readsubdir.h readsubdir.c fmt.h readsubdir.c scan.h readsubdir.c \
+str.h readsubdir.c auto_split.h readsubdir.c
+       ./compile readsubdir.c
+
+received.o: \
+compile received.c fmt.h received.c qmail.h substdio.h qmail.h \
+received.c now.h datetime.h now.h received.c datetime.h datetime.h \
+received.c date822fmt.h received.c received.h received.c
+       ./compile received.c
+
+remoteinfo.o: \
+compile remoteinfo.c remoteinfo.c remoteinfo.c remoteinfo.c \
+remoteinfo.c byte.h remoteinfo.c substdio.h remoteinfo.c ip.h \
+remoteinfo.c fmt.h remoteinfo.c timeoutconn.h remoteinfo.c \
+timeoutread.h remoteinfo.c timeoutwrite.h remoteinfo.c remoteinfo.h \
+remoteinfo.c
+       ./compile remoteinfo.c
+
+scan_8long.o: \
+compile scan_8long.c scan.h scan_8long.c
+       ./compile scan_8long.c
+
+scan_nbblong.o: \
+compile scan_nbblong.c scan.h scan_nbblong.c
+       ./compile scan_nbblong.c
+
+scan_ulong.o: \
+compile scan_ulong.c scan.h scan_ulong.c
+       ./compile scan_ulong.c
+
+seek.a: \
+makelib seek_cur.o seek_end.o seek_set.o seek_trunc.o
+       ./makelib seek.a seek_cur.o seek_end.o seek_set.o \
+       seek_trunc.o
+
+seek_cur.o: \
+compile seek_cur.c seek_cur.c seek.h seek_cur.c
+       ./compile seek_cur.c
+
+seek_end.o: \
+compile seek_end.c seek_end.c seek.h seek_end.c
+       ./compile seek_end.c
+
+seek_set.o: \
+compile seek_set.c seek_set.c seek.h seek_set.c
+       ./compile seek_set.c
+
+seek_trunc.o: \
+compile seek_trunc.c seek_trunc.c seek.h seek_trunc.c
+       ./compile seek_trunc.c
+
+select.h: \
+compile trysysel.c select.h1 select.h2
+       ( ./compile trysysel.c >/dev/null 2>&1 \
+       && cat select.h2 || cat select.h1 ) > select.h
+       rm -f trysysel.o trysysel
+
+sendmail: \
+load sendmail.o env.a getopt.a alloc.a substdio.a error.a str.a \
+auto_qmail.o
+       ./load sendmail env.a getopt.a alloc.a substdio.a error.a \
+       str.a auto_qmail.o 
+
+sendmail.o: \
+compile sendmail.c sgetopt.h subgetopt.h sgetopt.h sendmail.c \
+substdio.h sendmail.c subfd.h substdio.h substdio.h subfd.h \
+sendmail.c alloc.h sendmail.c auto_qmail.h sendmail.c exit.h \
+sendmail.c env.h sendmail.c str.h sendmail.c
+       ./compile sendmail.c
+
+setup: \
+it man conf-qmail
+       ./qmail-hier | ./install `head -1 conf-qmail`
+
+sgetopt.o: \
+compile sgetopt.c substdio.h sgetopt.c subfd.h substdio.h substdio.h \
+subfd.h sgetopt.c sgetopt.h sgetopt.h subgetopt.h sgetopt.h sgetopt.c \
+subgetopt.h subgetopt.h sgetopt.c
+       ./compile sgetopt.c
+
+shar: \
+FILES BLURB BLURB2 BLURB3 BLURB4 README FAQ INSTALL INSTALL.alias \
+INSTALL.boot INSTALL.ctl INSTALL.ids INSTALL.mbox INSTALL.qsmhook \
+UPGRADE THOUGHTS TODO THANKS CHANGES RFCHCSC RFCLOOPS RFCMXPS \
+RFCNETSTR RFCNRUDT RFCQMTP RFCQSBMF RFCVERP SECURITY INTERNALS FILES \
+VERSION SYSDEPS TARGETS Makefile conf-break auto_break.h conf-spawn \
+auto_spawn.h chkspawn.c conf-split auto_split.h conf-patrn \
+auto_patrn.h conf-users conf-groups auto_uids.h auto_usera.h extra.h \
+addresses.5 condredirect.1 dot-qmail.9 envelopes.5 forgeries.7 \
+forward.1 maildir2mbox.1 maildirmake.1 maildirwatch.1 mailsubj.1 \
+mbox.5 preline.1 qbiff.1 qlist.1 qmail-clean.8 qmail-command.8 \
+qmail-control.5 qmail-getpw.9 qmail-header.5 qmail-inject.8 \
+qmail-limits.9 qmail-local.8 qmail-log.5 qmail-lspawn.8 qmail-newu.8 \
+qmail-pop3d.8 qmail-popup.8 qmail-pw2u.9 qmail-qmtpd.8 qmail-qread.8 \
+qmail-qstat.8 qmail-queue.8 qmail-remote.8 qmail-rspawn.8 \
+qmail-send.9 qmail-showctl.8 qmail-smtpd.8 qmail-start.8 \
+qmail-tcpto.8 qmail-upgrade.9 qmail-users.5 qmail.7 qreceipt.1 \
+splogger.8 tcp-env.1 qmail-clean.c qmail-config.sh qmail-getpw.c \
+qmail-hier.c qmail-inject.c qmail-local.c qmail-lspawn.c qmail-newu.c \
+qmail-pop3d.c qmail-popup.c qmail-pw2u.c qmail-qmtpd.c qmail-qread.c \
+qmail-qstat.sh qmail-queue.c qmail-remote.c qmail-rspawn.c \
+qmail-send.c qmail-showctl.c qmail-smtpd.c qmail-start.c \
+qmail-tcpto.c spawn.c dnscname.c dnsfq.c dnsip.c dnsmxip.c dnsptr.c \
+hostname.c ipmeprint.c tcp-env.c sendmail.c qlist.c qreceipt.c \
+qsmhook.c qbiff.c forward.c preline.c predate.c condredirect.c \
+maildirmake.c maildir2mbox.c maildirwatch.c splogger.c qail.sh elq.sh \
+pinq.sh qlist2.sh qmail-upq.sh datemail.sh mailsubj.sh qlx.h \
+constmap.h constmap.c dnsdoe.h dnsdoe.c fmtqfn.h fmtqfn.c gfrom.h \
+gfrom.c myctime.h myctime.c newfield.h newfield.c qsutil.h qsutil.c \
+readsubdir.h readsubdir.c received.h received.c tcpto.h tcpto.c \
+tcpto_clean.c trigger.h trigger.c triggerpull.h triggerpull.c \
+trynpbg1.c trysyslog.c conf-cc conf-ld find-systype.sh \
+make-compile.sh make-load.sh make-makelib.sh trycpp.c warn-auto.sh \
+auto-str.c auto-int.c auto-int8.c auto-gid.c auto-uid.c install.c \
+instcheck.c alloc.3 alloc.h alloc.c alloc_re.c case.3 case.h \
+case_diffb.c case_diffs.c case_lowerb.c case_lowers.c case_starts.c \
+cdb.3 cdb.h cdb_hash.c cdb_seek.c cdb_unpack.c cdbmake.h \
+cdbmake_add.c cdbmake_hash.c cdbmake_pack.c cdbmss.h cdbmss.c coe.3 \
+coe.h coe.c fd.h fd_copy.3 fd_copy.c fd_move.3 fd_move.c fifo_make.3 \
+fifo.h fifo.c trymkffo.c fork.h1 fork.h2 tryvfork.c now.3 now.h now.c \
+open.h open_append.c open_excl.c open_read.c open_trunc.c \
+open_write.c seek.h seek_cur.c seek_end.c seek_set.c seek_trunc.c \
+conf-qmail auto_qmail.h qmail.h qmail.c gen_alloc.h gen_allocdefs.h \
+stralloc.3 stralloc.h stralloc_eady.c stralloc_pend.c stralloc_copy.c \
+stralloc_opyb.c stralloc_opys.c stralloc_cat.c stralloc_catb.c \
+stralloc_cats.c stralloc_arts.c strerr.h strerr_sys.c strerr_die.c \
+substdio.h substdio.c substdi.c substdo.c substdio_copy.c subfd.h \
+subfderr.c subfdouts.c subfdout.c subfdins.c subfdin.c readwrite.h \
+exit.h timeoutconn.h timeoutconn.c timeoutread.h timeoutread.c \
+timeoutwrite.h timeoutwrite.c remoteinfo.h remoteinfo.c uint32.h1 \
+uint32.h2 tryulong32.c wait.3 wait.h wait_pid.c wait_nohang.c \
+trywaitp.c sig.h sig_alarm.c sig_block.c sig_catch.c sig_pause.c \
+sig_pipe.c sig_child.c sig_term.c sig_hup.c sig_misc.c sig_bug.c \
+trysgact.c trysgprm.c env.3 env.h env.c envread.c byte.h byte_chr.c \
+byte_copy.c byte_cr.c byte_diff.c byte_rchr.c byte_zero.c str.h \
+str_chr.c str_cpy.c str_diff.c str_diffn.c str_len.c str_rchr.c \
+str_start.c lock.h lock_ex.c lock_exnb.c lock_un.c tryflock.c getln.3 \
+getln.h getln.c getln2.3 getln2.c sgetopt.3 sgetopt.h sgetopt.c \
+subgetopt.3 subgetopt.h subgetopt.c error.3 error_str.3 error_temp.3 \
+error.h error.c error_str.c error_temp.c fmt.h fmt_str.c fmt_strn.c \
+fmt_uint.c fmt_uint0.c fmt_ulong.c scan.h scan_ulong.c scan_8long.c \
+scan_nbblong.c slurpclose.h slurpclose.c quote.h quote.c hfield.h \
+hfield.c headerbody.h headerbody.c token822.h token822.c control.h \
+control.c datetime.3 datetime.h datetime.c datetime_un.c prioq.h \
+prioq.c date822fmt.h date822fmt.c dns.h dns.c trylsock.c tryrsolv.c \
+ip.h ip.c ipalloc.h ipalloc.c select.h1 select.h2 trysysel.c ndelay.h \
+ndelay.c ndelay_off.c direntry.3 direntry.h1 direntry.h2 trydrent.c \
+prot.h prot.c chkshsgr.c warn-shsgr tryshsgr.c ipme.h ipme.c \
+trysalen.c maildir.5 maildir.h maildir.c tcp-environ.5
+       shar -m `cat FILES` > shar
+       chmod 400 shar
+
+sig.a: \
+makelib sig_alarm.o sig_block.o sig_catch.o sig_pause.o sig_pipe.o \
+sig_child.o sig_hup.o sig_term.o sig_bug.o sig_misc.o
+       ./makelib sig.a sig_alarm.o sig_block.o sig_catch.o \
+       sig_pause.o sig_pipe.o sig_child.o sig_hup.o sig_term.o \
+       sig_bug.o sig_misc.o
+
+sig_alarm.o: \
+compile sig_alarm.c sig_alarm.c sig.h sig_alarm.c
+       ./compile sig_alarm.c
+
+sig_block.o: \
+compile sig_block.c sig_block.c sig.h sig_block.c hassgprm.h \
+sig_block.c
+       ./compile sig_block.c
+
+sig_bug.o: \
+compile sig_bug.c sig_bug.c sig.h sig_bug.c
+       ./compile sig_bug.c
+
+sig_catch.o: \
+compile sig_catch.c sig_catch.c sig.h sig_catch.c hassgact.h \
+sig_catch.c
+       ./compile sig_catch.c
+
+sig_child.o: \
+compile sig_child.c sig_child.c sig.h sig_child.c
+       ./compile sig_child.c
+
+sig_hup.o: \
+compile sig_hup.c sig_hup.c sig.h sig_hup.c
+       ./compile sig_hup.c
+
+sig_misc.o: \
+compile sig_misc.c sig_misc.c sig.h sig_misc.c
+       ./compile sig_misc.c
+
+sig_pause.o: \
+compile sig_pause.c sig_pause.c sig.h sig_pause.c hassgprm.h \
+sig_pause.c
+       ./compile sig_pause.c
+
+sig_pipe.o: \
+compile sig_pipe.c sig_pipe.c sig.h sig_pipe.c
+       ./compile sig_pipe.c
+
+sig_term.o: \
+compile sig_term.c sig_term.c sig.h sig_term.c
+       ./compile sig_term.c
+
+slurpclose.o: \
+compile slurpclose.c stralloc.h gen_alloc.h stralloc.h slurpclose.c \
+readwrite.h slurpclose.c slurpclose.h slurpclose.c
+       ./compile slurpclose.c
+
+socket.lib: \
+trylsock.c compile load
+       ( ( ./compile trylsock.c && \
+       ./load trylsock -lsocket -lnsl ) >/dev/null 2>&1 \
+       && echo -lsocket -lnsl || exit 0 ) > socket.lib
+       rm -f trylsock.o trylsock
+
+spawn.o: \
+compile chkspawn spawn.c spawn.c spawn.c sig.h spawn.c wait.h spawn.c \
+substdio.h spawn.c byte.h spawn.c str.h spawn.c stralloc.h \
+gen_alloc.h stralloc.h spawn.c select.h select.h select.h select.h \
+spawn.c exit.h spawn.c coe.h spawn.c open.h spawn.c error.h spawn.c \
+auto_qmail.h spawn.c auto_uids.h spawn.c auto_spawn.h spawn.c
+       ./chkspawn
+       ./compile spawn.c
+
+splogger: \
+load splogger.o substdio.a error.a str.a fs.a syslog.lib
+       ./load splogger substdio.a error.a str.a fs.a  `cat \
+       syslog.lib`
+
+splogger.0: \
+splogger.8
+       nroff -man splogger.8 > splogger.0
+
+splogger.o: \
+compile splogger.c splogger.c splogger.c splogger.c error.h \
+splogger.c substdio.h splogger.c subfd.h substdio.h substdio.h \
+subfd.h splogger.c exit.h splogger.c str.h splogger.c scan.h \
+splogger.c fmt.h splogger.c
+       ./compile splogger.c
+
+str.a: \
+makelib str_len.o str_diff.o str_diffn.o str_cpy.o str_chr.o \
+str_rchr.o str_start.o byte_chr.o byte_rchr.o byte_diff.o byte_copy.o \
+byte_cr.o byte_zero.o
+       ./makelib str.a str_len.o str_diff.o str_diffn.o str_cpy.o \
+       str_chr.o str_rchr.o str_start.o byte_chr.o byte_rchr.o \
+       byte_diff.o byte_copy.o byte_cr.o byte_zero.o
+
+str_chr.o: \
+compile str_chr.c str.h str_chr.c
+       ./compile str_chr.c
+
+str_cpy.o: \
+compile str_cpy.c str.h str_cpy.c
+       ./compile str_cpy.c
+
+str_diff.o: \
+compile str_diff.c str.h str_diff.c
+       ./compile str_diff.c
+
+str_diffn.o: \
+compile str_diffn.c str.h str_diffn.c
+       ./compile str_diffn.c
+
+str_len.o: \
+compile str_len.c str.h str_len.c
+       ./compile str_len.c
+
+str_rchr.o: \
+compile str_rchr.c str.h str_rchr.c
+       ./compile str_rchr.c
+
+str_start.o: \
+compile str_start.c str.h str_start.c
+       ./compile str_start.c
+
+stralloc.a: \
+makelib stralloc_eady.o stralloc_pend.o stralloc_copy.o \
+stralloc_opys.o stralloc_opyb.o stralloc_cat.o stralloc_cats.o \
+stralloc_catb.o stralloc_arts.o
+       ./makelib stralloc.a stralloc_eady.o stralloc_pend.o \
+       stralloc_copy.o stralloc_opys.o stralloc_opyb.o \
+       stralloc_cat.o stralloc_cats.o stralloc_catb.o \
+       stralloc_arts.o
+
+stralloc_arts.o: \
+compile stralloc_arts.c byte.h stralloc_arts.c str.h stralloc_arts.c \
+stralloc.h gen_alloc.h stralloc.h stralloc_arts.c
+       ./compile stralloc_arts.c
+
+stralloc_cat.o: \
+compile stralloc_cat.c byte.h stralloc_cat.c stralloc.h gen_alloc.h \
+stralloc.h stralloc_cat.c
+       ./compile stralloc_cat.c
+
+stralloc_catb.o: \
+compile stralloc_catb.c stralloc.h gen_alloc.h stralloc.h \
+stralloc_catb.c byte.h stralloc_catb.c
+       ./compile stralloc_catb.c
+
+stralloc_cats.o: \
+compile stralloc_cats.c byte.h stralloc_cats.c str.h stralloc_cats.c \
+stralloc.h gen_alloc.h stralloc.h stralloc_cats.c
+       ./compile stralloc_cats.c
+
+stralloc_copy.o: \
+compile stralloc_copy.c byte.h stralloc_copy.c stralloc.h gen_alloc.h \
+stralloc.h stralloc_copy.c
+       ./compile stralloc_copy.c
+
+stralloc_eady.o: \
+compile stralloc_eady.c alloc.h stralloc_eady.c stralloc.h \
+gen_alloc.h stralloc.h stralloc_eady.c gen_allocdefs.h \
+gen_allocdefs.h gen_allocdefs.h stralloc_eady.c
+       ./compile stralloc_eady.c
+
+stralloc_opyb.o: \
+compile stralloc_opyb.c stralloc.h gen_alloc.h stralloc.h \
+stralloc_opyb.c byte.h stralloc_opyb.c
+       ./compile stralloc_opyb.c
+
+stralloc_opys.o: \
+compile stralloc_opys.c byte.h stralloc_opys.c str.h stralloc_opys.c \
+stralloc.h gen_alloc.h stralloc.h stralloc_opys.c
+       ./compile stralloc_opys.c
+
+stralloc_pend.o: \
+compile stralloc_pend.c alloc.h stralloc_pend.c stralloc.h \
+gen_alloc.h stralloc.h stralloc_pend.c gen_allocdefs.h \
+gen_allocdefs.h gen_allocdefs.h stralloc_pend.c
+       ./compile stralloc_pend.c
+
+strerr.a: \
+makelib strerr_sys.o strerr_die.o
+       ./makelib strerr.a strerr_sys.o strerr_die.o
+
+strerr_die.o: \
+compile strerr_die.c substdio.h strerr_die.c subfd.h substdio.h \
+substdio.h subfd.h strerr_die.c exit.h strerr_die.c strerr.h \
+strerr_die.c
+       ./compile strerr_die.c
+
+strerr_sys.o: \
+compile strerr_sys.c error.h strerr_sys.c strerr.h strerr_sys.c
+       ./compile strerr_sys.c
+
+subfderr.o: \
+compile subfderr.c readwrite.h subfderr.c substdio.h subfderr.c \
+subfd.h substdio.h substdio.h subfd.h subfderr.c
+       ./compile subfderr.c
+
+subfdin.o: \
+compile subfdin.c readwrite.h subfdin.c substdio.h subfdin.c subfd.h \
+substdio.h substdio.h subfd.h subfdin.c
+       ./compile subfdin.c
+
+subfdins.o: \
+compile subfdins.c readwrite.h subfdins.c substdio.h subfdins.c \
+subfd.h substdio.h substdio.h subfd.h subfdins.c
+       ./compile subfdins.c
+
+subfdout.o: \
+compile subfdout.c readwrite.h subfdout.c substdio.h subfdout.c \
+subfd.h substdio.h substdio.h subfd.h subfdout.c
+       ./compile subfdout.c
+
+subfdouts.o: \
+compile subfdouts.c readwrite.h subfdouts.c substdio.h subfdouts.c \
+subfd.h substdio.h substdio.h subfd.h subfdouts.c
+       ./compile subfdouts.c
+
+subgetopt.o: \
+compile subgetopt.c subgetopt.h subgetopt.h subgetopt.c
+       ./compile subgetopt.c
+
+substdi.o: \
+compile substdi.c substdio.h substdi.c byte.h substdi.c error.h \
+substdi.c
+       ./compile substdi.c
+
+substdio.a: \
+makelib substdio.o substdi.o substdo.o subfderr.o subfdout.o \
+subfdouts.o subfdin.o subfdins.o substdio_copy.o
+       ./makelib substdio.a substdio.o substdi.o substdo.o \
+       subfderr.o subfdout.o subfdouts.o subfdin.o subfdins.o \
+       substdio_copy.o
+
+substdio.o: \
+compile substdio.c substdio.h substdio.c
+       ./compile substdio.c
+
+substdio_copy.o: \
+compile substdio_copy.c substdio.h substdio_copy.c
+       ./compile substdio_copy.c
+
+substdo.o: \
+compile substdo.c substdio.h substdo.c str.h substdo.c byte.h \
+substdo.c error.h substdo.c
+       ./compile substdo.c
+
+syslog.lib: \
+trysyslog.c compile load
+       ( ( ./compile trysyslog.c && \
+       ./load trysyslog -lgen ) >/dev/null 2>&1 \
+       && echo -lgen || exit 0 ) > syslog.lib
+       rm -f trysyslog.o trysyslog
+
+systype: \
+find-systype trycpp.c
+       ./find-systype > systype
+
+tcp-env: \
+load tcp-env.o dns.o remoteinfo.o timeoutread.o timeoutwrite.o \
+timeoutconn.o ip.o ipalloc.o case.a ndelay.a sig.a env.a getopt.a \
+stralloc.a alloc.a substdio.a error.a str.a fs.a dns.lib socket.lib
+       ./load tcp-env dns.o remoteinfo.o timeoutread.o \
+       timeoutwrite.o timeoutconn.o ip.o ipalloc.o case.a ndelay.a \
+       sig.a env.a getopt.a stralloc.a alloc.a substdio.a error.a \
+       str.a fs.a  `cat dns.lib` `cat socket.lib`
+
+tcp-env.0: \
+tcp-env.1
+       nroff -man tcp-env.1 > tcp-env.0
+
+tcp-env.o: \
+compile tcp-env.c tcp-env.c tcp-env.c tcp-env.c tcp-env.c sig.h \
+tcp-env.c stralloc.h gen_alloc.h stralloc.h tcp-env.c str.h tcp-env.c \
+env.h tcp-env.c fmt.h tcp-env.c scan.h tcp-env.c subgetopt.h \
+tcp-env.c ip.h tcp-env.c dns.h tcp-env.c byte.h tcp-env.c \
+remoteinfo.h tcp-env.c exit.h tcp-env.c case.h tcp-env.c
+       ./compile tcp-env.c
+
+tcp-environ.0: \
+tcp-environ.5
+       nroff -man tcp-environ.5 > tcp-environ.0
+
+tcpto.o: \
+compile tcpto.c tcpto.h tcpto.c open.h tcpto.c lock.h tcpto.c seek.h \
+tcpto.c now.h datetime.h now.h tcpto.c ip.h tcpto.c byte.h tcpto.c \
+datetime.h datetime.h tcpto.c readwrite.h tcpto.c
+       ./compile tcpto.c
+
+tcpto_clean.o: \
+compile tcpto_clean.c tcpto.h tcpto_clean.c open.h tcpto_clean.c \
+substdio.h tcpto_clean.c readwrite.h tcpto_clean.c
+       ./compile tcpto_clean.c
+
+timeoutconn.o: \
+compile timeoutconn.c timeoutconn.c timeoutconn.c timeoutconn.c \
+timeoutconn.c ndelay.h timeoutconn.c select.h select.h select.h \
+select.h timeoutconn.c error.h timeoutconn.c readwrite.h \
+timeoutconn.c ip.h timeoutconn.c byte.h timeoutconn.c timeoutconn.h \
+timeoutconn.c
+       ./compile timeoutconn.c
+
+timeoutread.o: \
+compile timeoutread.c timeoutread.h timeoutread.c select.h select.h \
+select.h select.h timeoutread.c error.h timeoutread.c readwrite.h \
+timeoutread.c
+       ./compile timeoutread.c
+
+timeoutwrite.o: \
+compile timeoutwrite.c timeoutwrite.h timeoutwrite.c select.h \
+select.h select.h select.h timeoutwrite.c error.h timeoutwrite.c \
+readwrite.h timeoutwrite.c
+       ./compile timeoutwrite.c
+
+token822.o: \
+compile token822.c stralloc.h gen_alloc.h stralloc.h token822.c \
+alloc.h token822.c str.h token822.c token822.h gen_alloc.h token822.h \
+token822.c gen_allocdefs.h gen_allocdefs.h gen_allocdefs.h token822.c
+       ./compile token822.c
+
+trigger.o: \
+compile trigger.c select.h select.h select.h select.h trigger.c \
+open.h trigger.c trigger.h trigger.c hasnpbg1.h trigger.c
+       ./compile trigger.c
+
+triggerpull.o: \
+compile triggerpull.c ndelay.h triggerpull.c open.h triggerpull.c \
+triggerpull.h triggerpull.c
+       ./compile triggerpull.c
+
+uint32.h: \
+tryulong32.c compile load uint32.h1 uint32.h2
+       ( ( ./compile tryulong32.c && ./load tryulong32 && \
+       ./tryulong32 ) >/dev/null 2>&1 \
+       && cat uint32.h2 || cat uint32.h1 ) > uint32.h
+       rm -f tryulong32.o tryulong32
+
+wait.a: \
+makelib wait_pid.o wait_nohang.o
+       ./makelib wait.a wait_pid.o wait_nohang.o
+
+wait_nohang.o: \
+compile wait_nohang.c wait_nohang.c wait_nohang.c haswaitp.h \
+wait_nohang.c
+       ./compile wait_nohang.c
+
+wait_pid.o: \
+compile wait_pid.c wait_pid.c wait_pid.c error.h wait_pid.c
+       ./compile wait_pid.c
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..929e25a
--- /dev/null
+++ b/README
@@ -0,0 +1,199 @@
+qmail 1.01
+19970413
+Copyright 1997
+D. J. Bernstein, qmail@pobox.com
+
+qmail is a secure, reliable, efficient, simple message transfer agent.
+It is meant as a replacement for the entire sendmail-binmail system on
+typical Internet-connected UNIX hosts. See BLURB, BLURB2, BLURB3, and
+BLURB4 for more detailed advertisements.
+
+INSTALL says how to set up and test qmail. If you're upgrading from
+1.00, read UPGRADE instead.
+
+See http://pobox.com/~djb/qmail.html for other qmail-related software
+and a pointer to the qmail mailing list.
+
+Other documentation here: RFC* explain solutions to several Internet
+mail problems; all of these except RFCMXPS are implemented in qmail.
+CHANGES and THANKS show how qmail has changed since it was first
+released. SECURITY, INTERNALS, THOUGHTS, and TODO record many of the
+qmail design decisions.
+
+The rest of this file is a list of systypes where various versions of
+qmail have been reported to work. 0.90 was the first gamma version. 0.96
+was the final gamma version. 1.00 had exactly the same code as 0.96. To
+see your systype, make systype; cat systype.
+
+1.00: a.ux-3.0-svr2-:-:-:mc68030-:- (tnx RF)
+0.96: aix-3-2-:-:-:000011216700-:- (tnx JLB)
+0.91: aix-3-2-:-:-:000109257500-:-
+0.96: aix-4-1-:-:-:000088581000-:- (tnx HJB)
+0.95: aix-4-1-:-:-:00061176a600-:- (tnx JS)
+1.00: aix-4-1-:-:-:00910033a000-:- (tnx K2J)
+0.91: aix-4-2-:-:-:006030934c00-:-
+1.00: bsd.os-2.0.1-:i386-:-:i486-:- (tnx KR)
+0.93: bsd.os-2.0.1-:i386-:-:pentium-:-
+0.96: bsd.os-2.1-:i386-:-:-:- (tnx DAR)
+1.00: bsd.os-2.1-:i386-:-:i486-:- (tnx RJC)
+0.96: bsd.os-2.1-:i386-:-:pentium-:- (tnx DAR)
+0.96: dgux-5.4r2.01-generic-:-:-:aviion-:- (tnx HWM)
+0.93: dgux-5.4r3.10-generic-:-:-:aviion-:- (tnx HWM)
+0.91: freebsd-2.0.5-release-:i386-:-:-:- (tnx TG)
+0.92: freebsd-2.1-stable-:i386-:-:pentium.815\100-:-
+0.90: freebsd-2.1.0-release-:i386-:-:cy486dlc-:- (tnx G2A)
+1.00: freebsd-2.1.0-release-:i386-:-:i486-dx2-:- (tnx JLB)
+1.00: freebsd-2.1.0-release-:i386-:-:i486-dx-:- (tnx chrisj=???)
+0.96: freebsd-2.1.0-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MBS)
+0.95: freebsd-2.1.5-release-:i386-:-:-:- (tnx NAA)
+0.96: freebsd-2.1.5-release-:i386-:-:i486-dx-:- (tnx FN)
+0.95: freebsd-2.1.5-release-:i386-:-:pentium.510\60.or.567\66-:- (tnx M2L)
+0.95: freebsd-2.1.5-release-:i386-:-:pentium.735\90-:- (tnx AG)
+0.92: freebsd-2.1.5-stable-:i386-:-:pentium.735\90.or.815\100-:- (tnx FE)
+1.00: freebsd-2.1.6-release-:i386-:-:-:- (tnx TM)
+0.96: freebsd-2.1.6-release-:i386-:-:Pentium-Pro.150-:- (tnx CH)
+0.96: freebsd-2.1.6-release-:i386-:-:i486-dx-:- (tnx HCJ)
+0.95: freebsd-2.1.6-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx M2L)
+0.96: freebsd-2.1.6.1-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx MF)
+1.00: freebsd-2.1.7-release-:i386-:-:pentium.735\90.or.815\100-:- (tnx JBB)
+0.95: freebsd-2.2-beta_a-:i386-:-:i486-dx2-:- (tnx DA)
+0.91: freebsd-2.2-current-:i386-:-:i486-dx-:- (tnx DC)
+1.00: freebsd-2.2-release-:i386-:-:-:- (tnx MT)
+1.00: freebsd-2.2.1-release-:i386-:-:-:- (tnx TM)
+1.00: freebsd-2.2.1-release-:i386-:-:i486-dx2-:- (tnx BR)
+1.00: freebsd-2.2.1-release-:i386-:-:pentium-:- (tnx IW)
+0.92: freebsd-3.0-current-:i386-:-:pentium-:- (tnx J2M)
+1.00: hp-ux-b.09.00-a-:-:-:9000.360-:- (tnx VV)
+0.91: hp-ux-b.10.01-a-:-:-:9000.712-:- (tnx S2R)
+0.94: hp-ux-b.10.01-a-:-:-:9000.715-:- (tnx BG)
+0.91: hp-ux-b.10.01-a-:-:-:9000.801-:- (tnx S2T)
+0.93: irix-5.3-02091401-:-:-:ip22-:- (tnx PW)
+0.91: irix-5.3-11091811-:sgi-:-:ip19-:- (tnx BS)
+0.96: irix-5.3-11091812-:-:-:ip22-:- (tnx JL)
+1.00: irix-6.2-03131015-:-:-:ip22-:- (tnx SAS)
+0.92: linux-1.2.13-:i386-:-:i386-:- (tnx RN)
+1.00: linux-1.2.13-:i386-:-:i486-:- (tnx RF)
+0.96: linux-1.2.13-:i386-:-:pentium-:- (tnx MEE)
+0.91: linux-1.3.90-:alpha-:-:alpha-:- (tnx ES)
+0.93: linux-1.99.6-:i386-:-:pentium-:- (tnx TG)
+0.94: linux-2.0.0-:i386-:-:i486-:- (tnx PCO)
+1.00: linux-2.0.0-:i386-:-:pentium-:- (tnx JJR)
+0.91: linux-2.0.1-:alpha-:-:alpha-:- (tnx BET)
+0.90: linux-2.0.1-:i386-:-:i486-:- (tnx DF)
+0.93: linux-2.0.1-:i386-:-:pentium-:- (tnx FW)
+0.95: linux-2.0.6-:i386-:-:pentium-:-
+1.00: linux-2.0.6-:i386-:-:ppro-:- (tnx MR)
+0.96: linux-2.0.7-:i386-:-:i486-:- (tnx TLM)
+0.90: linux-2.0.7-:i386-:-:pentium-:- (tnx K2J)
+0.94: linux-2.0.8-:i386-:-:i486-:- (tnx EP)
+0.90: linux-2.0.9-:i386-:-:pentium-:- (tnx JM)
+0.90: linux-2.0.10-:i386-:-:i486-:- (tnx JL)
+0.90: linux-2.0.10-:i386-:-:pentium-:- (tnx S2R)
+0.90: linux-2.0.11-:i386-:-:i486-:- (tnx ET)
+0.92: linux-2.0.12-:i386-:-:i486-:- (tnx TRR)
+0.90: linux-2.0.12-:i386-:-:ppro-:- (tnx SS)
+0.96: linux-2.0.13-:i386-:-:pentium-:- (tnx BW)
+0.90: linux-2.0.14-:i386-:-:pentium-:- (tnx PS)
+0.91: linux-2.0.15-:i386-:-:pentium-:- (tnx CL)
+0.90: linux-2.0.16-:i386-:-:i486-:- (tnx AP)
+0.91: linux-2.0.17-:i386-:-:i486-:- (tnx JL)
+0.91: linux-2.0.17-:i386-:-:pentium-:- (tnx SS)
+0.95: linux-2.0.18-:i386-:-:i386-:- (tnx RN)
+1.00: linux-2.0.18-:i386-:-:i486-:- (tnx JMS)
+1.00: linux-2.0.18-:i386-:-:pentium-:- (tnx D2S)
+0.92: linux-2.0.19-:alpha-:-:alpha-:- (tnx BET)
+0.92: linux-2.0.20-:i386-:-:pentium-:- (tnx RJH)
+0.92: linux-2.0.21-:i386-:-:i486-:- (tnx DCC)
+0.91: linux-2.0.21-:i386-:-:pentium-:- (tnx root@contact=???)
+0.92: linux-2.0.22-:i386-:-:i486-:- (tnx root@attila=???)
+1.00: linux-2.0.22-:i386-:-:pentium-:- (tnx MDI)
+1.00: linux-2.0.23-:i386-:-:i486-:- (tnx B2L)
+0.93: linux-2.0.23-:i386-:-:pentium-:- (tnx IW)
+0.93: linux-2.0.24-:i386-:-:i486-:- (tnx root@cerberus=???)
+1.00: linux-2.0.24-:i386-:-:pentium-:- (tnx VV)
+0.96: linux-2.0.25-:i386-:-:i486-:- (tnx BDB)
+1.00: linux-2.0.25-:i386-:-:pentium-:- (tnx AP)
+0.93: linux-2.0.25-:i386-:-:ppro-:- (tnx CS)
+0.93: linux-2.0.26-:i386-:-:i486-:- (tnx blynch=???)
+0.96: linux-2.0.26-:i386-:-:pentium-:- (tnx ESM)
+0.93: linux-2.0.26-:i386-:-:ppro-:- (tnx modus@enews=???)
+1.00: linux-2.0.27-:-:-:sparc-:- (tnx SVD)
+1.00: linux-2.0.27-:i386-:-:i486-:- (tnx FPL)
+1.00: linux-2.0.27-:i386-:-:pentium-:- (tnx  root@bullwinkle=???)
+1.00: linux-2.0.27-:i386-:-:ppro-:- (tnx DE)
+1.00: linux-2.0.28-:i386-:-:i486-:- (tnx H2S)
+1.00: linux-2.0.28-:i386-:-:pentium-:- (tnx root@duggy=???)
+1.00: linux-2.0.28-:i386-:-:ppro-:- (tnx KJS)
+1.00: linux-2.0.29-:i386-:-:i486-:- (tnx PK)
+1.00: linux-2.0.29-:i386-:-:pentium-:- (tnx CL)
+1.00: linux-2.0.29-:i386-:-:ppro-:- (tnx MMM)
+0.92: linux-2.1.1-:i386-:-:i486-:- (tnx nick@sga2=???)
+0.92: linux-2.1.7-:i386-:-:ppro-:- (tnx JL)
+0.92: linux-2.1.9-:i386-:-:pentium-:- (tnx AC)
+0.92: linux-2.1.10-:i386-:-:ppro-:- (tnx S3T)
+0.96: linux-2.1.13-:i386-:-:i486-:- (tnx ML)
+0.93: linux-2.1.13-:i386-:-:ppro-:- (tnx JL)
+0.96: linux-2.1.14-:i386-:-:pentium-:- (tnx SCW)
+0.96: linux-2.1.23-:i386-:-:pentium-:- (tnx JF)
+0.96: linux-2.1.25-:i386-:-:i486-:- (tnx JBF)
+0.96: linux-2.1.25-:i386-:-:pentium-:- (tnx UO)
+1.00: linux-2.1.26-:i386-:-:i486-:- (tnx DK)
+1.00: linux-2.1.27-:i386-:-:pentium-:- (tnx JF)
+1.00: linux-2.1.28-:i386-:-:pentium-:- (tnx RGS)
+1.00: linux-2.1.29-:i386-:-:i486-:- (tnx SJW)
+0.93: netbsd-1.2-:amiga-:-:amiga.4000.(m68040.cpu/mmu/fpu)-:- (tnx OS)
+0.96: netbsd-1.2-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx GH)
+0.91: netbsd-1.2-:mac68k-:-:apple.macintosh.se/30..(68030)-:- (tnx hauke=???)
+0.92: netbsd-1.2b-:i386-:-:-:- (tnx MG)
+0.93: netbsd-1.2b-:i386-:-:pentium.(genuineintel.586-class.cpu)-:- (tnx MG)
+0.96: netbsd-1.2c-:pmax-:-:-:- (tnx JLW)
+0.92: nextstep-3.3-:hppa-:-:7100lc-:-
+0.92: nextstep-3.3-:i386-:-:pentium-:-
+0.91: openbsd-1.2-:openbsd.m68k-:-:mac68k-:- (tnx AKB)
+0.95: openbsd-2.0-:openbsd.i386-:-:i386-:- (tnx JPH)
+0.96: openbsd-2.0-:openbsd.m68k-:-:mac68k-:- (tnx AKB)
+1.00: openbsd-2.0-hoth#0-:openbsd.i386-:-:i386-:- (tnx MBS)
+1.00: openbsd-2.0-mr_potatoe_head#2-:openbsd.i386-:-:i386-:- (JJMK)
+0.96: openbsd-2.0-puma#1-:openbsd.m68k-:-:mac68k-:- (tnx AB)
+0.92: osf1-v3.0-347-:-:-:alpha-:- (tnx B2H)
+0.92: osf1-v4.0-386-:-:-:alpha-:- (tnx WW)
+0.96: sco_sv-3.2-2-:-:-:i386-:- (tnx DEH)
+0.96: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4c-:sun4c- (tnx MBS)
+0.91: sunos-4.1.3_u1-1-:sparc-:sun4-:sun4m-:sun4m- (tnx IS)
+0.92: sunos-4.1.3_u1-2-:sparc-:sun4-:sun4c-:sun4c- (tnx ANR)
+0.93: sunos-4.1.3_u1-4-:sparc-:sun4-:sun4m-:sun4m- (tnx J2B)
+1.00: sunos-4.1.3_u1-4-:unknown-:sun4-:sun4m-:sun4m- (tnx J2B)
+0.91: sunos-4.1.3_u1-9-:sparc-:sun4-:sun4m-:sun4m- (tnx TA)
+0.91: sunos-4.1.4-2-:sparc-:sun4-:sun4c-:sun4c- (tnx ST)
+0.95: sunos-4.1.4-2-:sparc-:sun4-:sun4m-:sun4m- (tnx TG)
+0.95: sunos-4.1c-4.1.3-:sparc-:sun4-:sun4-:s4000- (tnx DBK)
+0.91: sunos-5.4-generic-:sparc-:sun4-:sun4m-:sun4m-
+0.96: sunos-5.4-generic_101945-10-:sparc-:sun4-:sun4m-:sun4m- (tnx W2K)
+0.93: sunos-5.4-generic_101945-23-:-:sun4-:sun4m-:sun4m- (tnx JP)
+0.92: sunos-5.4-generic_101945-23-:sparc-:-:sun4c-:- (tnx BH)
+1.00: sunos-5.4-generic_101945-34-:sparc-:sun4-:sun4m-:sun4m- (tnx ACB)
+0.92: sunos-5.4-generic_101945-36-:-:sun4-:sun4c-:sun4c- (tnx JP)
+0.95: sunos-5.4-generic_101945-43-:-:sun4-:sun4m-:sun4m- (tnx JP)
+0.90: sunos-5.4-generic_101946-07-:-:i86pc-:i86pc-:i86pc- (tnx CR)
+0.96: sunos-5.4-generic_101946-35-:i386-:i86pc-:i86pc-:i86pc- (tnx CK)
+0.96: sunos-5.5-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx M2G)
+0.93: sunos-5.5-generic-:sparc-:sun4-:sun4c-:sun4c- (tnx CG)
+1.00: sunos-5.5-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx SG)
+1.00: sunos-5.5-generic_103093-02-:sparc-:sun4-:sun4m-:sun4m- (tnx RF)
+0.91: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4d-:sun4d- (tnx M3S)
+0.96: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4m-:sun4m- (tnx RDM)
+0.91: sunos-5.5-generic_103093-03-:sparc-:sun4-:sun4u-:sun4u- (tnx olav=???)
+0.92: sunos-5.5-generic_103093-05-:sparc-:sun4-:sun4m-:sun4m- (tnx KE)
+0.93: sunos-5.5-generic_103093-06-:sparc-:sun4-:sun4d-:sun4d- (tnx KT)
+1.00: sunos-5.5-generic_103093-06-:sparc-:sun4-:sun4m-:sun4m- (tnx TEE)
+0.94: sunos-5.5-generic_103094-01-:i386-:i86pc-:i86pc-:i86pc- (tnx MD)
+0.91: sunos-5.5-generic_103094-03-:i386-:i86pc-:i86pc-:i86pc- (tnx ding=???)
+1.00: sunos-5.5.1-generic-:i386-:i86pc-:i86pc-:i86pc- (tnx DML)
+1.00: sunos-5.5.1-generic-:sparc-:sun4-:sun4m-:sun4m- (tnx JMT)
+1.00: sunos-5.5.1-generic-:sparc-:sun4-:sun4u-:sun4u- (tnx TH)
+0.96: sunos-5.5.1-generic_103640-02-:sparc-:sun4-:sun4m-:sun4m- (tnx SGC)
+0.95: sunos-5.5.1-generic_103640-03-:sparc-:sun4-:sun4m-:sun4m- (tnx MRG)
+1.00: sunos-5.5.1-generic_103640-03-:sparc-:sun4-:sun4u-:sun4u- (tnx EG)
+1.00: sunos-5.5.1-generic_103640-05-:sparc-:sun4-:sun4m-:sun4m- (tnx L2L)
+0.96: sunos-5.5.1-generic_patch-:i386-:i86pc-:i86pc-:i86pc- (tnx DK)
+0.96: ultrix-4.3-1-:pmax-:-:risc-:- (tnx YF)
diff --git a/RFCHCSC b/RFCHCSC
new file mode 100644 (file)
index 0000000..fba3184
--- /dev/null
+++ b/RFCHCSC
@@ -0,0 +1,37 @@
+The Hash Convention For Mail System Status Codes (HCMSSC)
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   RFC 1893 defines codes for mail delivery failures. For example,
+   code 5.1.1 means that the specified mailbox does not exist.
+
+   The qmail package sprays these codes all over the place, by adding a
+   code to the text of every error message, preceded by a hash mark and
+   surrounded by parentheses. It avoids using hash marks elsewhere.
+
+
+2. Examples
+
+   Here is a typical HCMSSC SMTP error message:
+
+      421 load average too high, please come back later (#4.3.2)
+
+   Here is part of a typical HCMSSC bounce message:
+
+      <mail-loop@silverton.berkeley.edu>:
+      This is looping; it already has my Delivered-To line. (#5.7.1)
+
+   But qmail doesn't use HCMSSC when it repeats another MTA's error
+   message:
+
+      <foo@heaven.af.mil>:
+      127.3.4.5 does not like recipient.
+      Remote host said: 550 <foo>... User unknown (#5.1.1)
+
+
+3. Security considerations
+
+   Don't take drastic action upon seeing "(#"; it might not be HCMSSC.
diff --git a/RFCLOOPS b/RFCLOOPS
new file mode 100644 (file)
index 0000000..0c4c91a
--- /dev/null
+++ b/RFCLOOPS
@@ -0,0 +1,338 @@
+Tools in the war on mail loops
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   An automailer means any program that receives a mail message and
+   automatically sends one or more mail messages. This term is meant to
+   include not only a mail-based server, such as a mailing list exploder
+   or a vacation program, but also an SMTP server, which receives a
+   message from the network and relays it to a local or remote user.
+
+   In a network full of automailers, any mistake can cause a mail loop.
+   Since some automailers generate several outputs in response to a
+   single input, a loop can produce an exponential explosion of mail.
+
+   All the automailers in the qmail package follow a general philosophy
+   designed to prevent mail loops and limit the damage from any loops
+   that do occur. These automailers have been repeatedly observed to
+   fail safe: they stop loops in the face of typical failures by other
+   hosts. This document explains the philosophy and describes the
+   automailers.
+
+   To some extent the philosophy here simply repeats and amplifies
+   standard practice as codified in RFC 974 and RFC 1123. Unfortunately,
+   the standards do not adequately control bounce loops, since they do
+   not recognize that postmasters want to see double bounces; they do
+   not adequately control relaying loops; and they do not prevent
+   cross-host forwarding loops.
+
+   Terminology: The mail message received by an automailer is called
+   input. The mail messages sent by an automailer are called outputs.
+   For simplicity, this document focuses on the case that the input has
+   just one envelope recipient.
+
+   REMINDER: This document describes the automailers in the qmail
+   package. Other packages include automailers that do not fit the
+   descriptions given here.
+
+   Beware that the war on mail loops can never be won: any method of
+   preventing mail loops can be subverted by other hosts. I welcome
+   further development of techniques that work well in practice.
+
+
+2. Basics
+
+   The output from an automailer is always further down the following
+   list than the input.
+
+      0 hops, <sender> is neither <> nor <#@[]>  normal messages
+      1 hop, <sender> is neither <> nor <#@[]>
+      2 hops, <sender> is neither <> nor <#@[]>
+      etc.
+      0 hops, <sender> is <>                     bounces
+      1 hop, <sender> is <>
+      2 hops, <sender> is <>
+      etc.
+      0 hops, <sender> is <#@[]>                 double bounces
+      1 hop, <sender> is <#@[]>
+      2 hops, <sender> is <#@[]>
+      etc.
+
+   Here sender means the envelope sender address. Hops means the number
+   of Received and Delivered-To fields in the header. See sections 3.3
+   and 3.4 for an explanation of <> and <#@[]>.
+
+   Consequently, no automailer ever generates an entirely new normal
+   message in response to a normal message. If the output is a normal
+   message, it always has more hops than the input.
+
+   When input and output are both normal messages, both bounces, or both
+   double bounces, the output header is essentially the same as the
+   input header. However, when an automailer moves from a normal message
+   to a bounce, or from a bounce to a double bounce, it generates an
+   entirely new header.
+
+   An automailer may refuse to operate if the input has too many hops.
+   The definition of too many hops depends on the automailer. This
+   practice is called hop counting. Note that some existing messages
+   legitimately take as many as 20 hops. One automailer uses a limit of
+   100 hops; this will be adequate for all messages in the foreseeable
+   future.
+
+   Hop counting is a weapon of last resort. It will, if correctly
+   implemented, prevent all infinite loops; however, even a finite loop
+   can do practically infinite damage, as illustrated in section 4.3.
+
+
+3. Pre-delivery automailers
+
+   Conceptually: The input is a message that has not yet reached its
+   envelope recipient address. It is fed to a relay, which attempts to
+   deliver the message directly to, or at least closer to, that address;
+   if the relay fails permanently, the message is fed to a bouncer or a
+   double-bouncer. Relays, bouncers, and double-bouncers are examples of
+   pre-delivery automailers.
+
+   A pre-delivery automailer produces at most one output.
+
+   The basic weapon against pre-delivery mail loops is gravity. A normal
+   message always moves closer to its envelope recipient, according to a
+   notion of distance defined in section 3.1. If it bounces before
+   reaching the recipient, it turns into a bounce message, which always
+   moves closer to the original envelope sender. If that in turn
+   bounces, it turns into a double bounce, which always moves closer to
+   a local postmaster. (Triple bounces do not exist.)
+
+
+3.1. Distance
+
+   The distance from a DNS domain D to a recipient U@R is defined as
+   follows, when R has an MX list: the minimum preference of D in the
+   MX list, or 100000 if D does not appear in the list.
+
+   When R has no MX records, the distance from R to U@R is defined as 0,
+   and the distance from any other domain to U@R is defined as 100000.
+
+   Exception: If R is an alias, i.e., if R has a CNAME record, the
+   distance from any domain to U@R is defined as 500000.
+
+   The distance from a host H to U@R is defined as the minimum distance
+   to U@R from any domain that touches H. (``D touches H'' means ``D has
+   an A record listing one of H's IP addresses.'')
+
+   Exception: If H does not accept mail from the network, its distance
+   to any recipient is defined as 999999.
+
+
+3.2. Relays
+
+   A relay is a pre-delivery automailer that sends the output towards
+   the envelope recipient. What this means for intra-host relays is not
+   discussed here. What this means for cross-host relays is the
+   following: if the relay is at host H, and it sends its output to host
+   T, then the distance from T to the output envelope recipient is
+   always smaller than the distance from H to the input envelope
+   recipient.
+
+   The following facts guarantee that certain cross-host relay behavior
+   is safe. For proofs of these facts, see Appendix A.
+
+      Fact 1: If R is an alias for X, X is not an alias, D touches T,
+      and T accepts mail from the network, then the distance from T to
+      U@X is smaller than the distance from H to U@R.
+
+      Fact 2: If R is not an alias, R has no MX records, H is not
+      touched by R, T is touched by R, and T accepts mail from the
+      network, then T is closer to U@R than H is.
+
+      Fact 3: If R is not an alias, R has an MX record with domain X and
+      preference p, H is not touched by any of the domains in the MX
+      list for R with preference <= p, T is touched by X, and T accepts
+      mail from the network, then T is closer to U@R than H is.
+
+   Also, a host that does not accept mail from the network can relay
+   messages to a nearby hub.
+
+   A relay adds a new Received header field to the top of the output.
+   Other than this, the output header, body, and envelope are exactly
+   the same as the input header, body, and envelope. Exception: If the
+   input envelope recipient is U@R, R is an alias for X, and X is not
+   an alias, the output envelope recipient is U@X.
+
+
+3.3. Bouncers
+
+   A bouncer is a pre-delivery automailer that lets the envelope sender
+   know what happened to a message. Most bouncers send failure notices.
+   Some bouncers, such as vacation servers and echo servers, send
+   success notices.
+
+   In a bouncer's output, the envelope sender is <>, and the envelope
+   recipient is the input envelope sender. A bouncer refuses to operate
+   if the input envelope sender is <> or <#@[]>.
+
+   Some mailers on the Internet do not understand the <> convention. In
+   fact, some mailers will rewrite <> as <@host>. So any message with an
+   envelope recipient of <> or <@host> is discarded upon local delivery.
+
+   Unlike a relay, a bouncer produces output with a new header, not
+   simply a copy of the input header. For example:
+
+      (envelope) from <> to <djb@silverton.berkeley.edu>
+      Date: 2 Jan 1996 03:38:25 GMT
+      From: DELIVERY NOTICE SYSTEM <MAILER-DAEMON@heaven.af.mil>
+      To: djb@silverton.berkeley.edu
+      Subject: failure notice
+
+   However, the body of the bounce indicates the relevant input envelope
+   recipient, as well as the Message-ID of the input, if the input had a
+   Message-ID. The body of a failure notice includes a copy of the
+   entire input message.
+
+
+3.4. Double-bouncers
+
+   A double-bouncer is a pre-delivery automailer that informs a local
+   postmaster of permanent failures to deliver bounce messages. Such
+   failures are generally caused by poorly configured hosts that produce
+   normal messages with faulty envelope sender addresses.
+
+   A double-bouncer refuses to operate unless the input envelope sender
+   is <>. The output envelope sender from a double-bouncer is <#@[]>;
+   note that <#@[]> cannot be used as an SMTP envelope sender under
+   RFC 821. The output envelope recipient is predetermined.
+
+   Note that double bounces are not suggested by RFC 1123. However,
+   faulty envelope sender addresses are usually configuration errors
+   that can and should be fixed. Some postmasters, faced with mail
+   software that throws away double bounces, resort to keeping copies of
+   all bounces; but single bounces are rarely the postmaster's problem.
+
+
+4. Post-delivery automailers
+
+   Conceptually: The input is a message that has reached its envelope
+   recipient address. It is fed to a post-delivery automailer at that
+   address.
+
+   The basic weapon against post-delivery loops is a new header field,
+   Delivered-To, tracing all the forwarders and mailing lists that a
+   message has been through. This field has the side benefit of making
+   it much easier for a user (or for a postmaster seeing a bounce) to
+   figure out the path that the message took. Delivered-To is similar to
+   RFC 1327's DL-Expansion-History, but (1) it omits the time stamp,
+   removing any need for parsing, and (2) it has a much better name.
+
+
+4.1. Exploders and repliers
+
+   There are two basic types of post-delivery automailers: exploders,
+   where the output envelope recipients are predetermined; and repliers,
+   where there is just one output, with envelope recipient determined
+   from the input.
+
+   Repliers normally determine the output envelope recipient as either
+   the input Reply-To header field, if it exists; or else the input
+   From header field, if it exists; or else the envelope sender. A
+   replier never produces an output to <> or <#@[]>.
+
+   Exploders are classified into mailing lists, where the output
+   envelope senders are predetermined, and forwarders, where every
+   output has envelope sender equal to the original envelope sender.
+
+   Exception: if the input envelope sender is <> or <#@[]>, then the
+   output envelope senders are equal to the input envelope sender, even
+   for a mailing list.
+
+   Note that, if the envelope sender of a mailing list with M bad
+   addresses is another exploder with E bad addresses, the local
+   postmaster will receive EM double bounces for each message to the
+   mailing list.
+
+
+4.2. Delivered-To
+
+   Every post-delivery automailer adds a new Delivered-To header field
+   to the top of each output.
+
+   The contents of the Delivered-To field are typically the address of
+   the automailer, i.e., the input envelope recipient, conventionally
+   without any quoting. The contents of the Delivered-To field are in
+   any case entirely predetermined. The automailer checks if exactly the
+   same Delivered-To field already appears in the header; if so, it
+   refuses to operate. 
+
+   A post-delivery automailer preserves existing Delivered-To and
+   Received fields. In fact, a post-delivery automailer generally
+   preserves all header fields. The exceptions are limited to known
+   fields that are not used for loop detection and that must be removed
+   for correct operation. For example, a replier generally changes the
+   body of a message and thus should not preserve the SVR4
+   Content-Length field.
+
+
+4.3. An example
+
+   Aliases and mailing lists are highly dangerous, because they can
+   generate several outputs for each input.
+
+   Here is an extreme example. A user has three accounts, and wants any
+   message to any of the accounts to be delivered to all three. So he
+   forwards luser@host1 to luser@host2 and luser@host3, forwards
+   luser@host2 to luser@host1 and luser@host3, and forwards luser@host3
+   to luser@host1 and luser@host2.
+
+   Without Delivered-To, someone who sends a message to luser@host1 will
+   receive a practically infinite series of bounces. For example, with a
+   hop count limit of 50, the sender will receive 1125899906842624
+   bounces.
+
+   If all the hosts, or two out of the three, support Delivered-To, the
+   message will bounce just a few times. If just one of the hosts
+   supports Delivered-To, it will be the unfortunate victim of a loop
+   between the other two hosts---although the total number of bounces
+   will drop from practically infinite down to a few hundred, with
+   typical hop count limits.
+
+
+Appendix A. Proofs of correctness for MX handling
+
+   Section 3.2 states three facts about the notion of distance defined
+   in section 3.1. Here are mathematical proofs of those facts.
+
+   Symbols: D, E, R, and X are domains; H and T are hosts; p and q are
+   nonnegative integers. {} is the empty set.
+
+   Hypotheses: M(R), the ``MX list for R,'' is a set of pairs (p,D)
+   where p <= 65535. There is a set A of domains, called ``aliases.''
+   There is a relation D->H, called ``D touches H.'' There is a set N of
+   hosts, called ``hosts that accept mail from the network.''
+
+   Definitions: m(D,R) = min { p: p = 100000 or (p,D) in M(R) } when
+   M(R) is nonempty. When M(R) is empty, m(D,R) is 0 if D = R, 100000
+   otherwise. f(D,R) is defined as 500000 if R is in A, m(D,R)
+   otherwise; this is the ``distance from D to U@R,'' for any U. g(H,R)
+   is defined as min { f(D,R): D->H } if H is in N, 999999 otherwise;
+   this is the ``distance from H to U@R,'' for any U.
+
+   Fact 1 (generalized): If R is in A, X is not in A, D->T, and T is in
+   N, then g(T,X) < g(H,R). Proof: R is in A, so f(E,R) = 500000 for any
+   E; thus g(H,R) >= 500000. X is not in A, so f(D,X) = m(D,X) <=
+   100000; hence g(T,X) <= f(D,X) <= 100000 < g(H,R).
+
+   Fact 2: If R is not in A, M(R) = {}, R->T, T is in N, and not R->H,
+   then g(T,R) < g(H,R). Proof: f(R,R) = m(R,R) = 0 since R is not in A
+   and M(R) = {}. T is in N so g(T,R) <= f(R,R) = 0 so g(T,R) = 0.
+   Suppose that g(H,R) <= g(T,R). Then g(H,R) = 0, so f(D,R) = 0 for
+   some D with D->H, so m(D,R) = 0. But then D = R by definition of m,
+   so R->H. Contradiction. Thus g(T,R) < g(H,R).
+
+   Fact 3: If R is not in A, (p,X) is in M(R), X->T, T is in N, and
+   (q,D) is not in M(R) whenever D->H and q <= p, then g(T,R) < g(H,R).
+   Proof: First m(X,R) <= p. R is not in A, so f(X,R) = m(X,R). T is in
+   N, so g(T,R) <= f(X,R). Thus g(T,R) <= p. Suppose that g(H,R) <= p.
+   Then f(D,R) <= p for some D with D->H, so m(D,R) <= p. But then
+   (m(D,R),D) is in M(R). Contradiction. Thus g(T,R) <= p < g(H,R).
diff --git a/RFCMXPS b/RFCMXPS
new file mode 100644 (file)
index 0000000..d0d3a70
--- /dev/null
+++ b/RFCMXPS
@@ -0,0 +1,122 @@
+The Mail Exchanger Protocol Switch (MXPS)
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   Mail messages today are transferred through the Simple Mail Transfer
+   Protocol (SMTP). One can imagine other protocols that achieve the
+   same results as SMTP but that, for example, use the network more
+   efficiently.
+
+   The Mail Exchanger Protocol Switch (MXPS) lets other protocols
+   compete with SMTP. A receiver can announce its support for another
+   protocol while operating properly with MXPS-ignorant senders. A
+   sender can check for support, with no overhead, while operating
+   properly with MXPS-ignorant receivers.
+
+   All receivers must support SMTP, i.e., must be able to receive
+   messages via SMTP. Similarly, all senders must be able to send
+   messages via SMTP.
+
+
+2. The protocol switch
+
+   MXPS abuses the preference field of MX records. A protocol is
+   assigned to each possible preference.
+
+   SMTP is assigned to preferences 0 through 10000.
+
+   The initial MXPS experiment will involve preferences between 12800
+   and 13055 inclusive. These preferences are sliced into 16 portions:
+
+      12800, 12816, 12832, 12848, 12864, ..., 13040: slice #0 (SMTP)
+      12801, 12817, 12833, 12849, 12865, ..., 13041: slice #1 (QMTP)
+      12802, 12818, 12834, 12850, 12866, ..., 13042: slice #2
+      ...
+      12815, 12831, 12847, 12863, 12879, ..., 13055: slice #15
+
+   Preferences in slice #0 are assigned SMTP. Preferences in slice #1
+   are assigned the Quick Mail Transfer Protocol (QMTP). Preferences in
+   the remaining slices may be assigned protocols in the future.
+
+   A receiver must support the protocol assigned to its preference. More
+   precisely, if an MX record points to domain D, and the MX preference
+   is assigned protocol P, then every host listed as an A record for D
+   must support protocol P.
+
+   When a sender, following the procedure outlined in RFC 974 (and
+   modified by RFC 1123), attempts to deliver a mail message as
+   specified by that MX record, it may use protocol P instead of SMTP.
+   If it does not support protocol P, it may treat the attempt as a
+   temporary failure and go on to the next MX record. However, the
+   sender must not skip every MX record.
+
+   MX records must never use unassigned preferences. A sender may treat
+   an unassigned preference as referring to SMTP.
+
+   Example: 
+
+      A.EXAMPLE.ORG  IN  MX  12801  A.EXAMPLE.ORG
+      B.EXAMPLE.ORG  IN  MX  12801  A.EXAMPLE.ORG
+                     IN  MX  12816  C.EXAMPLE.ORG
+
+   A sender with a message for A.EXAMPLE.ORG will try A.EXAMPLE.ORG by
+   QMTP. If it does not support QMTP, it will try SMTP instead. Note
+   that A.EXAMPLE.ORG must support both QMTP and SMTP.
+
+   A sender with a message for B.EXAMPLE.ORG will try A.EXAMPLE.ORG by
+   QMTP, then C.EXAMPLE.ORG by SMTP. If it does not support QMTP, it may
+   try SMTP instead of QMTP, or it may skip A.EXAMPLE.ORG.
+
+   Some of the above requirements might be violated if current
+   MXPS-ignorant domains use any preferences above 10000. Mail could be
+   unnecessarily rejected if any existing MXPS-ignorant domains have a
+   best-preference MX above 10000. I do not know any examples of such
+   domains.
+
+
+3. Protocol requirements
+
+   MXPS operates purely at the link level. It does not change the
+   fundamental nature of Internet mail.
+
+   The function of a mail transfer protocol is to transmit a message, as
+   described below, together with an envelope sender address and one or
+   more envelope recipient addresses.
+
+   A recipient address is a sequence of characters---i.e., nonnegative
+   integers---including an ASCII @ (64). It is parsed as box@dom, where
+   dom does not contain an @. The interpretation of box is up to the
+   hosts listed as MX records for dom. A sender address may contain an
+   @, in which case it is also of the form box@dom; or it may be a
+   special address, such as the empty string.
+
+   A mail message is structured as a sequence of lines. A line is a
+   sequence of characters. Every mail transfer protocol must be able to
+   transmit all sufficiently short boring mail messages. A boring mail
+   message is one where (1) no line has more than 80 characters and (2)
+   each character is either 9 or between 32 and 127 inclusive. Note that
+   RFC 1341 defines a mechanism for encoding a message with characters
+   between 0 and 255 inclusive as a boring mail message of similar
+   length.
+
+   The receiver must indicate, for each recipient address, either
+   acceptance, permanent rejection, or temporary rejection of the
+   message. Acceptance means that the receiver has taken responsibility,
+   in the sense of RFC 1123, section 5.3.3, for delivering the message
+   to that recipient. Rejection means that the receiver will not deliver
+   the message to that recipient.
+
+   Mail transfer protocols may vary in many details, such as line
+   encodings, the means of expressing acceptance or rejection, the
+   maximum number of allowable recipients per envelope, the encoding of
+   envelope addresses, the nature of optional protocol extensions, etc.
+
+
+4. Security considerations
+
+   MXPS does not change the following facts: An attacker who can subvert
+   the Domain Name System can steal or forge mail. An attacker who can
+   subvert TCP/IP can also steal or forge mail.
diff --git a/RFCNETSTR b/RFCNETSTR
new file mode 100644 (file)
index 0000000..6c02506
--- /dev/null
+++ b/RFCNETSTR
@@ -0,0 +1,88 @@
+Netstrings
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   A netstring is a self-delimiting encoding of a string. Netstrings are
+   very easy to generate and to parse. Any string may be encoded as a
+   netstring; there are no restrictions on length or on allowed bytes.
+   Another virtue of a netstring is that it declares the string size up
+   front. Thus an application can check in advance whether it has enough
+   space to store the entire string.
+
+   Netstrings may be used as a basic building block for reliable network
+   protocols. Most high-level protocols, in effect, transmit a sequence
+   of strings; those strings may be encoded as netstrings and then
+   concatenated into a sequence of characters, which in turn may be
+   transmitted over a reliable stream protocol such as TCP.
+
+   Note that netstrings can be used recursively. The result of encoding
+   a sequence of strings is a single string. A series of those encoded
+   strings may in turn be encoded into a single string. And so on.
+
+   In this document, a string of 8-bit bytes may be written in two
+   different forms: as a series of hexadecimal numbers between angle
+   brackets, or as a sequence of ASCII characters between double quotes.
+   For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of
+   length 12; it is the same as the string "hello world!".
+
+   Although this document restricts attention to strings of 8-bit bytes,
+   netstrings could be used with any 6-bit-or-larger character set.
+
+
+2. Definition
+
+   Any string of 8-bit bytes may be encoded as [len]":"[string]",".
+   Here [string] is the string and [len] is a nonempty sequence of ASCII
+   digits giving the length of [string] in decimal. The ASCII digits are
+   <30> for 0, <31> for 1, and so on up through <39> for 9. Extra zeros
+   at the front of [len] are prohibited: [len] begins with <30> exactly
+   when [string] is empty.
+
+   For example, the string "hello world!" is encoded as <31 32 3a 68
+   65 6c 6c 6f 20 77 6f 72 6c 64 21 2c>, i.e., "12:hello world!,". The
+   empty string is encoded as "0:,".
+
+   [len]":"[string]"," is called a netstring. [string] is called the
+   interpretation of the netstring.
+
+
+3. Sample code
+
+   The following C code starts with a buffer buf of length len and
+   prints it as a netstring.
+
+      if (printf("%lu:",len) < 0) barf();
+      if (fwrite(buf,1,len,stdout) < len) barf();
+      if (putchar(',') < 0) barf();
+
+   The following C code reads a netstring and decodes it into a
+   dynamically allocated buffer buf of length len.
+
+      if (scanf("%9lu",&len) < 1) barf();  /* >999999999 bytes is bad */
+      if (getchar() != ':') barf();
+      buf = malloc(len + 1);       /* malloc(0) is not portable */
+      if (!buf) barf();
+      if (fread(buf,1,len,stdin) < len) barf();
+      if (getchar() != ',') barf();
+
+   Both of these code fragments assume that the local character set is
+   ASCII, and that the relevant stdio streams are in binary mode.
+
+
+4. Security considerations
+
+   The famous Finger security hole may be blamed on Finger's use of the
+   CRLF encoding. In that encoding, each string is simply terminated by
+   CRLF. This encoding has several problems. Most importantly, it does
+   not declare the string size in advance. This means that a correct
+   CRLF parser must be prepared to ask for more and more memory as it is
+   reading the string. In the case of Finger, a lazy implementor found
+   this to be too much trouble; instead he simply declared a fixed-size
+   buffer and used C's gets() function. The rest is history.
+
+   In contrast, as the above sample code shows, it is very easy to
+   handle netstrings without risking buffer overflow. Thus widespread
+   use of netstrings may improve network security.
diff --git a/RFCNRUDT b/RFCNRUDT
new file mode 100644 (file)
index 0000000..4f3196a
--- /dev/null
+++ b/RFCNRUDT
@@ -0,0 +1,89 @@
+Notice-Requested-Upon-Delivery-To (NRUDT)
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   The UNIX sendmail program has for many years supported a
+   Return-Receipt-To (RRT) header field that requests a notice of
+   successful final delivery.
+
+   Notice-Requested-Upon-Delivery-To (NRUDT) has the same basic
+   function. The big difference is that RRT lists the sender's address,
+   while NRUDT lists the recipient's address.
+
+   This change is critical. RRT works poorly for messages to multiple
+   recipients, because it requests a notice from every recipient. RRT in
+   a message to a large mailing list produces a giant, usually
+   unintentional, flood of mail. This problem is so severe that RRT has
+   been disabled in recent versions of sendmail.
+
+   NRUDT is designed to be adopted immediately, with minimal disruption,
+   as a solution to the problems of RRT. Note that NRUDT is merely a
+   request for notification; unlike the link-level Delivery Status
+   Notification SMTP extension, NRUDT does not provide a guarantee of
+   notification.
+
+   NRUDT is supported by the qreceipt program in the qmail package.
+
+
+2. Syntax
+
+   NRUDT is a field in the header of an RFC 822 mail message. It has the
+   following syntax:
+
+      "Notice-Requested-Upon-Delivery-To" ":" 1#address
+
+   See RFC 822 for more information about header fields and addresses.
+
+   NRUDT requests that, upon final delivery of the message to any of the
+   specified addresses, the sender be notified. Note that more than one
+   address can appear in a single NRUDT header field. Multiple NRUDT
+   header fields should not appear in a single message.
+
+
+3. Response
+
+   Upon successful final delivery of a message to any address listed in
+   an NRUDT header field, the host performing delivery may, if desired,
+   generate a success notice.
+
+   The success notice is similar to a failure notice as described in RFC
+   1123. Its envelope sender is <>. Its envelope recipient is the
+   envelope sender of the original message; however, if the envelope
+   sender of the original message is <>, a success notice is not sent.
+
+   The body of the success notice does not contain a copy of the
+   original message, but it does indicate the Message-ID of the original
+   message, as well as the relevant recipient address.
+
+   A success notice may indicate delivery to several addresses. For
+   example, given the following message:
+
+      (envelope) from djb@silverton.berkeley.edu 
+      (envelope) to god@heaven.af.mil, angels@heaven.af.mil
+      Date: 1 Jan 1996 21:43:34 GMT
+      From: "D. J. Bernstein" <djb@silverton.berkeley.edu>
+      Message-Id: <19960101214334.8529.qmail@silverton.berkeley.edu>
+      Notice-Requested-Upon-Delivery-To: God <god@heaven.af.mil>,
+        angels@heaven.af.mil (You Know Who You Are)
+      ...
+
+   a host may respond as follows:
+
+      (envelope) from <> to djb@silverton.berkeley.edu
+      Date: 1 Jan 1996 21:43:37 GMT
+      From: DELIVERY NOTICE SYSTEM <MAILER-DAEMON@heaven.af.mil>
+      To: djb@silverton.berkeley.edu
+      Subject: success notice
+
+      I delivered <19960101214334.8529.qmail@silverton.berkeley.edu>
+      to the following local mailboxes:
+
+         god@heaven.af.mil
+         angels@heaven.af.mil
+
+      Thanks for asking.
+
+   However, a success notice is never merged with a failure notice.
diff --git a/RFCQMTP b/RFCQMTP
new file mode 100644 (file)
index 0000000..6c48e98
--- /dev/null
+++ b/RFCQMTP
@@ -0,0 +1,229 @@
+Quick Mail Transfer Protocol (QMTP)
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   The Quick Mail Transfer Protocol (QMTP) is a replacement for the
+   Simple Mail Transfer Protocol (SMTP). QMTP eliminates any need for
+   end-of-line scanning between hosts with the same end-of-line
+   convention. It features automatic pipelining and chunking, 8-bit
+   transmission, prior declaration of the message size, and efficient
+   batching. It is designed to be very easy to implement.
+
+   QMTP is supported by the qmail-qmtpd and maildir2qmtp programs in the
+   qmail package.
+
+   In this document, a string of 8-bit bytes may be written in two
+   different forms: as a series of hexadecimal numbers between angle
+   brackets, or as a sequence of ASCII characters between double quotes.
+   For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of
+   length 12; it is the same as the string "hello world!". Note that
+   these notations are part of this document, not part of the protocol.
+
+
+2. Protocol
+
+   A QMTP client connects to a QMTP server, as discussed in section 7,
+   over a reliable stream protocol allowing transmission of 8-bit bytes.
+
+   Protocol outline: the client sends one or more packages; after each
+   package, the server sends back some responses.
+
+   The client begins by sending a package. A package contains a mail
+   message, an envelope sender address, and one or more envelope
+   recipient addresses. See section 4 for the format of a package.
+
+   When the server sees the end of the package, it sends back a series
+   of responses, one response for each envelope recipient address, in
+   the same order as given by the client. The server is not permitted to
+   change the order under any circumstances, even if two addresses are
+   the same. See section 5 for the format of a response.
+
+   The server is not permitted to send any portion of its responses to a
+   package until the client has sent the final byte of the package. The
+   client is permitted to close the connection before sending the final
+   byte of the package; in this case, the server must throw away the
+   package without attempting to deliver the message. However, the
+   server must not throw away previously accepted messages.
+
+   The client does NOT need to wait for a server response before sending
+   another package. The server must NOT throw away incoming data when it
+   sends a response. It is the client's responsibility to avoid
+   deadlock: if it sends a package before receiving all expected server
+   responses, it must continuously watch for those responses. The server
+   is permitted to delay its responses if further data has already shown
+   up from the client; while it is delaying responses, it must not pause
+   to wait for further data for the client.
+
+   The server is permitted to close the connection at any time, although
+   high-quality servers will try to avoid doing so. Any response not
+   received by the client indicates a temporary failure.
+
+   A QMTP session should take at most 1 hour. Both sides are expected
+   to close the connection after this time.
+
+
+3. Messages
+
+   In this document, an ``8-bit mail message'' means a sequence of
+   lines. Each line is a string of zero or more 8-bit bytes.
+
+   A message is called ``safe'' if none of its bytes are <0a>.
+
+   Implementation note: Here is the intended interpretation of text
+   files as messages under some current operating systems. Under DOS, a
+   message is stored on disk as
+
+      first line, <0d 0a>, second line, <0d 0a> ... <0d 0a>, last line.
+
+   Under UNIX, a message is stored on disk as
+
+      first line, <0a>, second line, <0a> ... <0a>, last line.
+
+   Notice that both of these encodings are reversible for safe messages.
+
+   In practice, it is very common for the last line to be empty. Many
+   existing utilities refer to the last line as a ``partial line'' and
+   ignore it whether or not it is empty.
+
+
+4. Packages
+
+   A package is the concatenation of three strings:
+
+      first, an encoded 8-bit mail message;
+      second, an encoded envelope sender address;
+      third, an encoded series of encoded envelope recipient addresses.
+
+   Each envelope address is a string of 8-bit bytes. The interpretation
+   of addresses depends on the environment in which QMTP is used and is
+   outside the scope of this document. Each address is encoded as a
+   netstring, as discussed in section 6. The series of encoded recipient
+   addresses is in turn encoded as a netstring.
+
+   A message is encoded as a string of 8-bit bytes in one of two ways:
+
+      Encoding #1 is <0d>, the first line, <0d 0a>, the second line,
+      <0d 0a>, the third line, ..., <0d 0a>, the last line.
+
+      Encoding #2 is <0a>, the first line, <0a>, the second line, <0a>,
+      the third line, ..., <0a>, the last line.
+
+   This string of 8-bit bytes is in turn encoded as a netstring, as
+   discussed in section 6.
+
+   Every server must be prepared to handle encoding #1 and encoding #2.
+   A server must not reject a message merely because of its encoding.
+
+   Implementation note: The intent of encoding #1 and encoding #2 is to
+   allow very straightforward handling of text files under DOS and UNIX
+   respectively. The programmer can print <0d> or <0a> and then simply
+   copy the file.
+
+
+5. Responses
+
+   Each response is a nonempty string of 8-bit bytes, encoded as a
+   netstring. The first byte of the string is one of the following:
+
+      "K" The message has been accepted for delivery to this envelope
+          recipient. This is morally equivalent to the 250 response to
+          DATA in SMTP; it is subject to the reliability requirements
+          of RFC 1123, section 5.3.3.
+
+      "Z" Temporary failure. The client should try again later.
+
+      "D" Permanent failure.
+
+   The remaining bytes are a description of what happened. It is
+   expected that the description, when interpreted as UTF-2 characters,
+   (1) will be human-readable, (2) will not repeat the envelope
+   recipient address, and (3) will not include formatting characters
+   other than <20>. However, these expectations are not requirements,
+   and the client should be ready for arbitrary bytes from the server.
+
+   Descriptions beginning with <20> are reserved for future extensions.
+   In descriptions not beginning with <20>, the character "#" must not
+   appear except in HCMSSC codes.
+
+   A server must NOT accept a safe message unless it can store the
+   message without corruption. More precisely: if the encoded message
+   sent by the client matches the encoding of some safe message M, then
+   acceptance means that the server is accepting responsibility to
+   deliver M to the envelope recipient. (There is at most one
+   possibility for M, since encodings are reversible on safe messages.)
+   Deletion of nulls is NOT permissible; a server that deletes nulls
+   must reject any message containing nulls. Folding of long lines and
+   high-bit stripping are also NOT permissible.
+
+   Servers are permitted to change unsafe messages.
+
+
+6. Netstrings
+
+   Any string of 8-bit bytes may be encoded as [len]":"[string]",".
+   Here [string] is the string and [len] is a nonempty sequence of ASCII
+   digits giving the length of [string] in decimal. The ASCII digits are
+   <30> for 0, <31> for 1, and so on up through <39> for 9. Extra zeros
+   at the front of [len] are prohibited: [len] begins with <30> exactly
+   when [string] is empty.
+
+   For example, the string "hello world!" is encoded as <31 32 3a 68
+   65 6c 6c 6f 20 77 6f 72 6c 64 21 2c>, i.e., "12:hello world!,". The
+   empty string is encoded as "0:,".
+
+   [len]":"[string]"," is called a netstring. [string] is called the
+   interpretation of the netstring.
+
+
+7. Encapsulation
+
+   QMTP may be used on top of TCP. A QMTP-over-TCP server listens for
+   TCP connections on port 209.
+
+
+8. Examples
+
+   A client opens a connection and sends the concatenation of the
+   following strings:
+
+      "246:" <0a>
+         "Received: (qmail-queue invoked by uid 0);"
+         " 29 Jul 1996 09:36:40 -0000" <0a>
+         "Date: 29 Jul 1996 11:35:35 -0000" <0a>
+         "Message-ID: <19960729113535.375.qmail@heaven.af.mil>" <0a>
+         "From: God@heaven.af.mil" <0a>
+         "To: djb@silverton.berkeley.edu (D. J. Bernstein)" <0a>
+         <0a>
+         "This is a test." <0a> ","
+      "24:" "God-DSN-37@heaven.af.mil" ","
+      "30:" "26:djb@silverton.berkeley.edu," ","
+
+      "356:" <0d>
+         "From: MAILER-DAEMON@heaven.af.mil" <0d 0a>
+         "To:" <0d 0a>
+         "   Hate." <22> "The Quoting" <22>
+        "@SILVERTON.berkeley.edu," <0d 0a>
+         "   " <22> "\\Backslashes!" <22>
+        "@silverton.BERKELEY.edu" <0d 0a>
+         <0d 0a>
+         "The recipient addresses here could"
+        " have been encoded in SMTP as" <0d 0a>
+         "" <0d 0a>
+         "   RCPT TO:<Hate.The\ Quoting@silverton.berkeley.EDU>" <0d 0a>
+         "   RCPT TO:<\\Backslashes!@silverton.berkeley.edu>" <0d 0a>
+         <0d 0a>
+         "This ends with a partial last line, right here" ","
+      "0:" ","
+      "83:" "39:Hate.The Quoting@silverton.berkeley.edu,"
+         "36:\Backslashes!@silverton.berkeley.EDU," ","
+      
+   The server sends the following response, indicating acceptance:
+
+      "21:Kok 838640135 qp 1390,"
+      "21:Kok 838640135 qp 1391,"
+      "21:Kok 838640135 qp 1391,"
+
+   The client closes the connection.
diff --git a/RFCQSBMF b/RFCQSBMF
new file mode 100644 (file)
index 0000000..de132a7
--- /dev/null
+++ b/RFCQSBMF
@@ -0,0 +1,155 @@
+The qmail-send Bounce Message Format (QSBMF)
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   When a message transport agent (MTA) finds itself permanently unable
+   to deliver a mail message, it generates a new message, generally
+   known as a bounce message, back to the envelope sender.
+
+   Bounce messages produced by the qmail-send program display the list
+   of failed recipient addresses, an explanation for each address, and a
+   copy of the original message, in a format that is easy for both
+   humans and programs to read. For example:
+
+      Date: 17 Mar 1996 03:54:40 -0000
+      From: MAILER-DAEMON@silverton.berkeley.edu
+      To: djb@silverton.berkeley.edu
+      Subject: failure notice
+      
+      Hi. This is the qmail-send program at silverton.berkeley.edu.
+      I'm afraid I wasn't able to deliver your message to the
+      following addresses. This is a permanent error; I've given up.
+      Sorry it didn't work out.
+      
+      <god@heaven.af.mil>:
+      Sorry, I couldn't find any host by that name.
+      
+      --- Below this line is a copy of the message.
+      
+      Return-Path: <djb@silverton.berkeley.edu>
+      Received: (qmail 317 invoked by uid 7); 17 Mar 1996 03:54:38 -0000
+      Date: 17 Mar 1996 03:54:38 -0000
+      Message-ID: <19960317035438.316.qmail@silverton.berkeley.edu>
+      From: djb@silverton.berkeley.edu (D. J. Bernstein)
+      To: god@heaven.af.mil
+      Subject: are you there?
+      
+      Just checking.
+
+   This document defines qmail-send's format for bounce messages.
+
+   In this document, a string of 8-bit bytes may be written in two
+   different forms: as a series of hexadecimal numbers between angle
+   brackets, or as a sequence of ASCII characters between double quotes.
+   For example, <68 65 6c 6c 6f 20 77 6f 72 6c 64 21> is a string of
+   length 12; it is the same as the string "hello world!".
+
+
+2. Format
+
+   A bounce message may be recognized as QSBMF as follows: its body
+   begins with the characters "Hi. This is the" exactly as shown.
+
+   The body of the message has four pieces: an introductory paragraph,
+   zero or more recipient paragraphs, a break paragraph, and the
+   original message.
+
+   Each paragraph is a series of non-blank lines followed by a single
+   blank line. The break paragraph begins with the character "-". All
+   other paragraphs begin with characters other than "-". The break
+   paragraph is human-readable but provides no interesting information.
+
+   The introductory paragraph is human-readable. It gives the name and
+   human-comprehensible location of the MTA, but parsers should not
+   attempt to use this information.
+
+   The only type of recipient paragraph described here is a failure
+   paragraph, which begins with the character "<". Paragraphs beginning
+   with other characters are reserved for future extensions.
+
+   The first line of a failure paragraph ends with the characters ">:".
+   Everything between the leading "<" and the trailing ">:" is an
+   (unquoted) Internet mail address.
+
+   A failure paragraph asserts that the MTA was permanently unable to
+   deliver the message to the mail address shown on the first line; the
+   MTA will not attempt further deliveries to that address. The
+   remaining lines of the paragraph give a human-readable description of
+   the reason for failure. Descriptions beginning with <20>, and
+   descriptions containing "#", are reserved for future extensions.
+
+   The envelope sender might not have sent his message to the address
+   shown. There are two reasons for this. First, the MTA may freely
+   replace unprintable characters with "_". Second, the original 
+   recipient address may have been an alias for the address shown.
+
+   The original message is an exact copy of the message received by the
+   MTA, including both header and body, preceded by a Return-Path field
+   showing the envelope sender.
+
+
+3. Comparison with 1892/1894
+
+   RFC 1892 and RFC 1894 together describe a format for delivery status
+   notifications. I have decided not to use that format, because I
+   believe that its complexity will prevent wide implementation and
+   increase the burden on people who manage mailing lists.
+
+   QSBMF is dedicated to failure reports, whereas RFC 1894 allows
+   success reports and deferral reports. Although it would be possible
+   to add deferral paragraphs and success paragraphs to QSBMF, it would
+   be even easier to design separate formats for such notices. I have
+   trouble reading mixed failure/deferral reports.
+
+   QSBMF always returns the entire original message. RFC 1892 allows
+   the MTA to return nothing or to return just the headers; it states
+   ``Return of content may be wasteful of network bandwidth.'' However,
+   failure notices are very rare, so the overall loss of bandwidth in
+   this case is insignificant. A much more important issue is storage
+   space: someone who manages a big mailing list does not want to have
+   to store several copies of each message in the form of bounces. The
+   best solution is to have each bounce automatically fed through a
+   program that stores only the critical information. I expect such
+   programs to spring up quickly for QSBMF.
+
+   RFC 1894 provides language-independent error messages, as described
+   by RFC 1893. One can achieve the same results more easily by adding
+   structure to the human-readable failure descriptions, for example
+   with HCMSSC.
+
+   RFC 1894 is able to communicate an ``envelope ID'' and the original
+   envelope recipient address specified by the sender. Unfortunately,
+   this information will almost never be available, since it requires
+   support by every intermediate MTA. All of the applications of this
+   information can be handled reliably, right now, with VERPs; this
+   requires support from the sender's MTA but not from other hosts.
+
+   RFC 1894 includes several pieces of information that might be of
+   human interest but can be seen just as easily from Received lines:
+   the name of the MTA where delivery failed, the name of the previous
+   MTA, timestamps, etc.
+
+   All of these RFC 1894 features have a cost: complexity. A program
+   cannot parse an 1894 report without parsing RFC 822 header fields
+   and understanding quite a bit of MIME. This will limit the
+   availability of parsing software. In the meantime, such reports are
+   annoying to mailing list maintainers, since they are full of
+   uninteresting information and are difficult to parse visually.
+
+
+4. Security considerations
+
+   Bounce messages may be forged. Never remove someone from a mailing
+   list without sending him a message stating that you are doing so,
+   even if the reason for removal is a series of apparent bounce
+   messages from his address.
+
+   If you send a message along a secret path, you should change the
+   envelope sender address of the message to yourself, so that a bounce
+   will not reveal anything to the original sender. In other words: for
+   secret forwarding, use a mailing list, not a forwarder.
+
+   See RFC 1894 for further discussion of these points.
diff --git a/RFCVERP b/RFCVERP
new file mode 100644 (file)
index 0000000..6299a44
--- /dev/null
+++ b/RFCVERP
@@ -0,0 +1,88 @@
+Variable Envelope Return Paths
+D. J. Bernstein, djb@pobox.com
+19970201
+
+
+1. Introduction
+
+   The fundamental problem in managing a large mailing list is matching
+   bounce messages to subscription addresses.
+
+   Often a bounce message refers to a failing address that does not
+   appear on the mailing list. One of the mailing list subscribers is
+   forwarding messages to that address. Which subscriber? As the list
+   grows, this question becomes more and more difficult to answer.
+
+   Sometimes a bounce message doesn't identify the address that failed.
+   On occasion it doesn't even include a copy of the original message.
+   See RFC 1211 for an extensive collection of horror stories.
+
+   In theory, one could solve this problem with the DSN option and DSN
+   format described in RFC 1891, RFC 1892, and RFC 1894. Unfortunately,
+   the DSN option is useless unless it is supported by every
+   intermediate MTA. The complexity of RFC 1891 means that it will be
+   many years, perhaps infinitely many, before DSNs are universally
+   supported. Furthermore, the complexity of RFC 1894 means that parsing
+   the subscriber address is difficult even on the occasions that the
+   address is available.
+
+   Variable envelope return paths (VERPs) completely eliminate this
+   problem _right now_. They automatically and reliably identify the
+   subscription address relevant to each bounce message. They provide
+   the address in a form that is trivial for automated bounce handlers
+   to parse. They require support from the local mailer, but they do not
+   require support from any other hosts.
+
+
+2. Variable envelope return paths
+
+   Here is how VERPs work: each recipient of the message sees a
+   different envelope sender address. When a message to the
+   djb-sos@silverton.berkeley.edu mailing list is sent to
+   God@heaven.af.mil, for example, it has the following envelope sender:
+
+      djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu
+
+   If the message bounces, the bounce message will be sent back to
+   djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu.
+
+   If God is forwarding His mail, the bounce message will still go to
+   djb-sos-owner-God=heaven.af.mil@silverton.berkeley.edu. No matter how
+   uninformative the bounce message is, it will display God's
+   subscription address in its envelope.
+
+   Another benefit of VERPs is that God Himself can see what address He
+   used to subscribe.
+
+   Making VERPs work requires two pieces of local software support.
+   First: it must be easy to modify the outgoing sender address
+   separately for each envelope recipient. For example, with one mailer,
+   qmail, a user can simply touch ~/.qmail-list-owner and
+   ~/.qmail-list-owner-default to apply VERPs to user-list.
+
+   Second, and more important: it must be easy to identify a collection
+   of addresses, such as djb-sos-owner-*, and send all mail for those
+   addresses to one place, while preserving the * information. Under
+   qmail, all user-list-owner-* mail will be sent to the user once he
+   touches ~/.qmail-list-owner-default. Sending the mail through an
+   automated bounce-handling program is just as easy.
+
+   With older mailers, applying VERPs would require setting up a new
+   user-list-owner-recipient alias for each new recipient. This
+   inconvenience has prevented VERPs from being widely exploited, even
+   though the idea is not new.
+
+
+3. Per-message VERPs
+
+   VERPs are not restricted to distinguishing mailing list subscribers;
+   they can also be used to distinguish messages.
+
+   For example, a user can send one message with an envelope sender
+   address of user-dsn-1, the next message with user-dsn-2, and so on.
+   As long as the local mailer gives all user-dsn-* back to that user,
+   he can reliably match up incoming bounces with outgoing messages.
+
+   Per-message VERPs can be combined with per-recipient VERPs. Every
+   application of RFC 1891's ORCPT and ENVID can be handled with
+   VERPs---easily, reliably, and right now.
diff --git a/SECURITY b/SECURITY
new file mode 100644 (file)
index 0000000..218830d
--- /dev/null
+++ b/SECURITY
@@ -0,0 +1,131 @@
+Background: Every few months CERT announces Yet Another Security Hole In
+Sendmail---something that lets local or even remote users take complete
+control of the machine. I'm sure there are many more holes waiting to be
+discovered; sendmail's design means that any minor bug in 46000 lines of
+code is a major security risk. Other popular mailers, such as Smail, and
+even mailing-list managers, such as Majordomo, seem nearly as bad.
+
+I started working on qmail because I was sick of this cycle of doom.
+Here are some of the things I did to make sure that qmail will never let
+an intruder into your machine.
+
+
+1. Programs and files are not addresses. Don't treat them as addresses.
+
+sendmail treats programs and files as addresses. Obviously random people
+can't be allowed to execute arbitrary programs or write to arbitrary
+files, so sendmail goes through horrendous contortions trying to keep
+track of whether a local user was ``responsible'' for an address. This
+has proven to be an unmitigated disaster.
+
+In qmail, programs and files are not addresses. The local delivery
+agent, qmail-local, can run programs or write to files as directed by
+~user/.qmail, but it's always running as that user. (The notion of
+``user'' is configurable, but root is never a user. To prevent silly
+mistakes, qmail-local makes sure that neither ~user nor ~user/.qmail is
+group-writable or world-writable.)
+
+Security impact: .qmail, like .cshrc and .exrc and various other files,
+means that anyone who can write arbitrary files as a user can execute
+arbitrary programs as that user. That's it.
+
+
+2. Do as little as possible in setuid programs.
+
+A setuid program must operate in a very dangerous environment: a user is
+under complete control of its fds, args, environ, cwd, tty, rlimits,
+timers, signals, and more. Even worse, the list of controlled items
+varies from one vendor's UNIX to the next, so it is very difficult to
+write portable code that cleans up everything.
+
+Of the twelve most recent sendmail security holes, six worked only
+because the entire sendmail system is setuid.
+
+Only one qmail program is setuid: qmail-queue. Its only purpose is to
+add a new mail message to the outgoing queue.
+
+
+3. Do as little as possible as root.
+
+The entire sendmail system runs as root, so there's no way that its
+mistakes can be caught by the operating system's built-in protections.
+In contrast, only two qmail programs, qmail-start and qmail-lspawn,
+run as root.
+
+
+4. Move separate functions into mutually untrusting programs.
+
+Five of the qmail programs---qmail-smtpd, qmail-send, qmail-rspawn,
+qmail-remote, and tcp-env---are not security-critical. Even if all of
+these programs are completely compromised, so that an intruder has
+control over the qmaild, qmails, and qmailr accounts and the mail queue,
+he still can't take over your system. None of the other programs trust
+the results from these five.
+
+In fact, these programs don't even trust each other. They are in three
+groups: tcp-env and qmail-smtpd, which run as qmaild; qmail-rspawn and
+qmail-remote, which run as qmailr; and qmail-send, the queue manager,
+which runs as qmails. Each group is immune from attacks by the others.
+
+(From root's point of view, as long as root doesn't send any mail, only
+qmail-start and qmail-lspawn are security-critical. They don't write any
+files or start any other programs as root.)
+
+
+5. Don't parse.
+
+I have discovered that there are two types of command interfaces in the
+world of computing: good interfaces and user interfaces.
+
+The essence of user interfaces is _parsing_---converting an unstructured
+sequence of commands, in a format usually determined more by psychology
+than by solid engineering, into structured data.
+
+When another programmer wants to talk to a user interface, he has to
+_quote_: convert his structured data into an unstructured sequence of
+commands that the parser will, he hopes, convert back into the original
+structured data.
+
+This situation is a recipe for disaster. The parser often has bugs: it
+fails to handle some inputs according to the documented interface. The
+quoter often has bugs: it produces outputs that do not have the right
+meaning. Only on rare joyous occasions does it happen that the parser
+and the quoter both misinterpret the interface in the same way.
+
+When the original data is controlled by a malicious user, many of these
+bugs translate into security holes. Some examples: the Linux login
+-froot security hole; the classic find | xargs rm security hole; the
+recent Majordomo security hole. Even a simple parser like getopt is
+complicated enough for people to screw up the quoting.
+
+In qmail, all the internal file structures are incredibly simple: text0
+lines beginning with single-character commands. (text0 format means that
+lines are separated by a 0 byte instead of line feed.) The program-level
+interfaces don't take options.
+
+All the complexity of parsing RFC 822 address lists and rewriting
+headers is in the qmail-inject program, which runs without privileges
+and is essentially part of the UA.
+
+The only nasty case is .qmail, qmail's answer to .forward. I tried to
+make this as simple as possible, but unfortunately it still has to be
+edited by users. As a result, the qlist mailing-list-management program
+has to be careful to exclude subscriber addresses that contain newlines.
+
+
+6. Keep it simple, stupid.
+
+See BLURB for some of the reasons that qmail is so much smaller than
+sendmail. There's nothing inherently complicated about writing a mailer.
+(Except RFC 822 support; but that's only in qmail-inject.) Security
+holes can't show up in features that don't exist. 
+
+
+7. Write bug-free code.
+
+I've mostly given up on the standard C library. Many of its facilities,
+particularly stdio, seem designed to encourage bugs. A big chunk of
+qmail is stolen from a basic C library that I've been developing for
+several years for a variety of applications. The stralloc concept and
+getline2() make it very easy to avoid buffer overruns, memory leaks,
+and artificial line length limits.
diff --git a/SYSDEPS b/SYSDEPS
new file mode 100644 (file)
index 0000000..0bb01ec
--- /dev/null
+++ b/SYSDEPS
@@ -0,0 +1,17 @@
+VERSION
+systype
+hasshsgr.h
+hasnpbg1.h
+select.h
+hasflock.h
+hassalen.h
+fork.h
+hassgact.h
+direntry.h
+hassgprm.h
+haswaitp.h
+hasmkffo.h
+uint32.h
+dns.lib
+socket.lib
+syslog.lib
diff --git a/TARGETS b/TARGETS
new file mode 100644 (file)
index 0000000..12054c0
--- /dev/null
+++ b/TARGETS
@@ -0,0 +1,353 @@
+auto-ccld.sh
+make-load
+find-systype
+systype
+load
+make-compile
+compile
+fork.h
+qmail-local.o
+qmail.o
+auto-str.o
+make-makelib
+makelib
+substdio.o
+substdi.o
+substdo.o
+subfderr.o
+subfdout.o
+subfdouts.o
+subfdin.o
+subfdins.o
+substdio_copy.o
+substdio.a
+error.o
+error_str.o
+error_temp.o
+error.a
+str_len.o
+str_diff.o
+str_diffn.o
+str_cpy.o
+str_chr.o
+str_rchr.o
+str_start.o
+byte_chr.o
+byte_rchr.o
+byte_diff.o
+byte_copy.o
+byte_cr.o
+byte_zero.o
+str.a
+auto-str
+auto_qmail.c
+auto_qmail.o
+auto-int8.o
+fmt_str.o
+fmt_strn.o
+fmt_uint.o
+fmt_uint0.o
+fmt_ulong.o
+scan_ulong.o
+scan_8long.o
+scan_nbblong.o
+fs.a
+auto-int8
+auto_patrn.c
+auto_patrn.o
+quote.o
+now.o
+gfrom.o
+myctime.o
+slurpclose.o
+case_diffb.o
+case_diffs.o
+case_lowerb.o
+case_lowers.o
+case_starts.o
+case.a
+getln.o
+getln2.o
+getln.a
+subgetopt.o
+sgetopt.o
+getopt.a
+sig_alarm.o
+hassgprm.h
+sig_block.o
+hassgact.h
+sig_catch.o
+sig_pause.o
+sig_pipe.o
+sig_child.o
+sig_hup.o
+sig_term.o
+sig_bug.o
+sig_misc.o
+sig.a
+open_append.o
+open_excl.o
+open_read.o
+open_trunc.o
+open_write.o
+open.a
+seek_cur.o
+seek_end.o
+seek_set.o
+seek_trunc.o
+seek.a
+hasflock.h
+lock_ex.o
+lock_exnb.o
+lock_un.o
+lock.a
+fd_copy.o
+fd_move.o
+fd.a
+wait_pid.o
+haswaitp.h
+wait_nohang.o
+wait.a
+env.o
+envread.o
+env.a
+stralloc_eady.o
+stralloc_pend.o
+stralloc_copy.o
+stralloc_opys.o
+stralloc_opyb.o
+stralloc_cat.o
+stralloc_cats.o
+stralloc_catb.o
+stralloc_arts.o
+stralloc.a
+alloc.o
+alloc_re.o
+alloc.a
+datetime.o
+datetime_un.o
+datetime.a
+qmail-local
+uint32.h
+qmail-lspawn.o
+auto-uid.o
+auto-uid
+auto-gid.o
+auto-gid
+auto_uids.c
+auto_uids.o
+auto-int.o
+auto-int
+auto_spawn.c
+auto_spawn.o
+select.h
+chkspawn.o
+chkspawn
+spawn.o
+chkshsgr.o
+chkshsgr
+hasshsgr.h
+prot.o
+coe.o
+cdb_hash.o
+cdb_unpack.o
+cdb_seek.o
+cdb.a
+qmail-lspawn
+qmail-getpw.o
+auto_break.c
+auto_break.o
+auto_usera.c
+auto_usera.o
+qmail-getpw
+qmail-remote.o
+control.o
+constmap.o
+timeoutread.o
+timeoutwrite.o
+timeoutconn.o
+tcpto.o
+dns.o
+ip.o
+ipalloc.o
+hassalen.h
+ipme.o
+ndelay.o
+ndelay_off.o
+ndelay.a
+socket.lib
+dns.lib
+qmail-remote
+qmail-rspawn.o
+tcpto_clean.o
+qmail-rspawn
+direntry.h
+qmail-clean.o
+auto_split.c
+auto_split.o
+fmtqfn.o
+qmail-clean
+qmail-send.o
+qsutil.o
+newfield.o
+prioq.o
+hasmkffo.h
+fifo.o
+hasnpbg1.h
+trigger.o
+readsubdir.o
+date822fmt.o
+qmail-send
+qmail-start.o
+qmail-start
+splogger.o
+syslog.lib
+splogger
+qmail-queue.o
+triggerpull.o
+qmail-queue
+qmail-inject.o
+headerbody.o
+hfield.o
+token822.o
+qmail-inject
+predate.o
+predate
+datemail
+mailsubj
+qmail-upq
+qmail-config
+qmail-showctl.o
+qmail-showctl
+qmail-newu.o
+cdbmss.o
+cdbmake_pack.o
+cdbmake_hash.o
+cdbmake_add.o
+cdbmake.a
+qmail-newu
+qmail-pw2u.o
+qmail-pw2u
+qmail-qread.o
+qmail-qread
+qmail-qstat
+qmail-tcpto.o
+qmail-tcpto
+qmail-pop3d.o
+qmail-pop3d
+qmail-popup.o
+qmail-popup
+qmail-qmtpd.o
+received.o
+qmail-qmtpd
+qmail-smtpd.o
+qmail-smtpd
+sendmail.o
+sendmail
+tcp-env.o
+remoteinfo.o
+tcp-env
+dnscname.o
+dnsdoe.o
+dnscname
+dnsptr.o
+dnsptr
+dnsip.o
+dnsip
+dnsmxip.o
+dnsmxip
+dnsfq.o
+dnsfq
+hostname.o
+hostname
+ipmeprint.o
+ipmeprint
+qlist.o
+qlist
+qlist2
+qreceipt.o
+qreceipt
+qsmhook.o
+qsmhook
+qbiff.o
+qbiff
+forward.o
+forward
+preline.o
+preline
+condredirect.o
+condredirect
+maildirmake.o
+maildirmake
+maildir2mbox.o
+maildir.o
+strerr_sys.o
+strerr_die.o
+strerr.a
+maildir2mbox
+maildirwatch.o
+maildirwatch
+qail
+elq
+pinq
+qmail-hier.o
+qmail-hier
+install.o
+install
+instcheck.o
+instcheck
+it
+qmail-local.0
+qmail-lspawn.0
+qmail-getpw.8
+qmail-getpw.0
+qmail-remote.0
+qmail-rspawn.0
+qmail-clean.0
+qmail-send.8
+qmail-send.0
+qmail-start.0
+splogger.0
+qmail-queue.0
+qmail-inject.0
+mailsubj.0
+qmail-showctl.0
+qmail-newu.0
+qmail-pw2u.8
+qmail-pw2u.0
+qmail-qread.0
+qmail-qstat.0
+qmail-tcpto.0
+qmail-pop3d.0
+qmail-popup.0
+qmail-qmtpd.0
+qmail-smtpd.0
+tcp-env.0
+qlist.0
+qreceipt.0
+qbiff.0
+forward.0
+preline.0
+condredirect.0
+maildirmake.0
+maildir2mbox.0
+maildirwatch.0
+qmail.0
+qmail-upgrade.7
+qmail-upgrade.0
+qmail-limits.7
+qmail-limits.0
+qmail-log.0
+qmail-control.0
+qmail-header.0
+qmail-users.0
+dot-qmail.5
+dot-qmail.0
+qmail-command.0
+tcp-environ.0
+maildir.0
+mbox.0
+addresses.0
+envelopes.0
+forgeries.0
+man
diff --git a/THANKS b/THANKS
new file mode 100644 (file)
index 0000000..c3d5ba0
--- /dev/null
+++ b/THANKS
@@ -0,0 +1,217 @@
+Thanks to lots of people for success and failure reports, code, ideas,
+and documentation. See CHANGES for details of specific contributions.
+Sorry if I left anyone out.
+
+SA = Satoshi Adachi
+G1A = Graham Adams
+NA = Norm Aleks
+NAA = Nicholas A. Amato
+G2A = Greg Andrews
+TA = Tetsuo Aoki
+DA = Dave Arcuri
+JJB = J. J. Bailey
+J2B = Jos Backus
+SSB = Stik Bakken
+J1B = John Banghart
+GB = Glenn Barry
+JLB = Julie L. Baumler
+SLB = Steven L. Baur
+ALB = Allan L. Bazinet
+JDHB = Johannes D. H. Beekhuizen
+BDB = Boris D. Beletsky
+HJB = Herbert J. Bernstein
+REB = Ronald E. Bickers
+JPB = Joe Block
+BB = Bruce Bodger
+SB = Stephane Bortzmeyer
+PB = Peter Bowyer
+ACB = Andy C. Brandt
+AB = Alan Briggs
+AKB = Allen K. Briggs
+JBB = Jason B. Brown
+JAB = Jeremy A. Bussard
+RJC = Robert J. Carter
+EC = Evan Champion
+JC = Jim Clausing
+HCJ = Helio Coelho Jr.
+BC = Bob Collie
+SGC = Stephen G. Comings
+MC = Michael Cooley
+DCC = Daniel C. Cotey
+AC = Arne Coucheron
+M2C = Mark Crimmins
+DC = Dan Cross
+MD = Mark Delany
+SVD = Stef Van Dessel
+RD = Rahul Dhesi
+MJD = Mark-Jason Dominus
+JD = Joe Doupnik
+FE = Frank Ederveen
+DE = Daniel Egnor
+MWE = Mark W. Eichin
+MEE = Mads E. Eilertsen
+KE = Kenny Elliott
+TEE = Thomas E. Erskine
+ME = Marc Ewing
+JF = Janos Farkas
+DF = Dale Farnsworth
+YF = Yaroslav Faybishenko
+CF = C. Ferree
+JBF = John B. Fleming
+C2F = Chuck Foster
+RF = Rainer Fraedrich
+MF = Massimo Fusaro
+CG = Chris Garrigues
+BG = Bert Gijsbers
+M2G = Michael R. Gile
+EG = Eivind Gjelseth
+HG = Howard Goldstein
+TG = Tim Goodwin
+HDG = Hans de Graaff
+MG = Michael Graff
+PJG = Paul Graham
+MRG = Matthew R. Green
+SG = Steven Grimm
+AG = Armin Gruner
+CSH = Clayton S. Haapala
+JLH = Jason L. Haar
+MLH = May Liss Haarstad
+CH = Chael Hall
+JPH = Justin P. Hannah
+RJH = Randy Harmon
+PH = Paul Harrington
+DEH = Daniel E. Harris
+RFH = Robert F. Harrison
+D1H = Dieter Heidner
+GH = Gene Hightower
+MH = Markus Hofmann
+D2H = Dan Hollis
+NH = Nick Holloway
+TH = Ton Hospel
+BH = Brad Howes
+TJH = Timothy J. Hunt
+B2H = Buck Huppmann
+MDI = Miguel de Icaza
+BJ = Brian Jackson
+AJ = Alan Jaffray
+CEJ = Colin Eric Johnson
+K2J = Kevin Johnson
+K1J = Kyle Jones
+SJ = Sudish Joseph
+W2K = Wolfram Kahl
+JJMK = Jonathan J. M. Katz
+CK = Christoph Kaesling
+PK = Petri Kaukasoina
+D2K = Dax Kelson
+TK = Terry Kennedy
+DBK = Douglas B. Kerry
+WK = Werner Koch
+DK = Dave Kopper
+J1K = Jost Krieger
+J2K = Johannes Kroeger
+EK = Eric Krohn
+B2L = Brent Laminack
+AL = Andreas Lamprecht
+L2L = Louis Larry
+M3L = Michael Lazarou
+CL = Carsten Leonhardt
+JRL = John R. Levine
+DML = David M. Lew
+LL = lilo
+FPL = Frederik P. Lindberg
+JL = Jim Littlefield
+BL = Brian Litzinger
+RL = Robert Luce
+ML = Martin Lucina
+M2L = M. Lyons
+TM = Toshinori Maeno
+ESM = Edward S. Marshall
+J2M = Joel Maslak
+TLM = Timothy L. Mayo
+DM = David Mazieres
+RM = Rich McClellan
+SM = Shawn McHorse
+JM = Jim Meehan
+HWM = Henry W. Miller
+RDM = Raul Miller
+MMM = Momchil M. Momchev
+JGM = John G. Myers
+FN = Faried Nawaz
+RN = Russell Nelson
+TN = Thomas Neumann
+UO = Uwe Ohse
+PCO = Peter C. Olsen
+HHO = Harald Hanche-Olsen
+BEO = Bruce E. O'Neel
+JP = John Palkovic
+AP = Andrew Pam
+SP = Stephen Parker
+TVP = Tom van Peer
+BP = Bruce Perens
+CMP = Chase M. Phillips
+DP = Dave Platt
+EP = Emanuele Pucciarelli
+JPR = Jean-Pierre Radley
+S1R = Satish Ramachandran
+MR = Mosfeq Rashid
+TRR = Tracy R. Reed
+BR = Brian Reichert
+S2R = Sean Reifschneider
+DAR = Daniel A. Reish
+CR = Christian Riede
+KR = Kenji Rikitake
+OR = Ollivier Robert
+ANR = Adriano Nagelschmidt Rodrigues
+JJR = Jaron J. Rubenstein
+RS = Robert Sanders
+KJS = Kevin J. Sawyer
+MBS = Mike Scher
+SAS = Steven A. Schrader
+CLS = Christopher L. Seawood
+OS = Oliver Seiler
+D2S = Dan Senie
+SS = Simon Shapiro
+RGS = Richard G. Sharman
+DS = Dave Sill
+H2S = Harley Silver
+M3S = Morten Skjelland
+JS = Jesper Skriver
+ES = Eric Smith
+IS = Icarus Sparry
+CS = Cloyce Spradling
+BS = Bjoern Stabell
+HS = Harlan Stenn
+JMS = Jason M. Stokes
+DWS = David Wayne Summers
+M2S = Mikael Suokas
+PS = Paul Svensson
+ET = Eivind Tagseth
+PT = Paul Taylor
+S2T = Steve Taylor
+BT = Brad Templeton
+DST = Daniel S. Thibadeau
+MT = Mark Thompson
+S3T = Steffen Thorsen
+KT = Karsten Thygesen
+BET = Bennett E. Todd
+JMT = John M. Twilley
+ST = Steve Tylock
+TU = Tetsu Ushijima
+VV = Vince Vielhaber
+TV = Tommi Virtanen
+FW = Frank Wagner
+BW = Bill Weinman
+IW = Ian Westcott
+SJW = Stephen J. White
+JW = John Whittaker
+LW = Lionel Widdifield
+MW = Mate Wierdl
+BTW = Brian T. Wightman
+PW = Peter Wilkinson
+HW = Hal Wine
+GAW = Greg A. Woods
+SCW = Steven C. Work
+JLW = Jason L. Wright
+WW = Wei Wu
+IKW = Ian Keith Wynne
+BZ = Blaz Zupan
diff --git a/THOUGHTS b/THOUGHTS
new file mode 100644 (file)
index 0000000..6587084
--- /dev/null
+++ b/THOUGHTS
@@ -0,0 +1,437 @@
+Please note that this file is not called ``Internet Mail For Dummies.''
+It _records_ my thoughts on various issues. It does not _explain_ them.
+Paragraphs are not organized except by section. The required background
+varies wildly from one paragraph to the next.
+
+In this file, ``sendmail'' means Allman's creation; ``sendmail-clone''
+means the program in this package.
+
+
+1. Security
+
+There are lots of interesting remote denial-of-service attacks on any
+mail system. A long-term solution is to insist on prepayment for
+unauthorized resource use. The tricky technical problem is to make the
+prepayment enforcement mechanism cheaper than the expected cost of the
+attacks. (For local denial-of-service attacks it's enough to be able to
+figure out which user is responsible.)
+
+qmail-send's log was originally designed for profiling. It subsequently
+sprouted some tracing features. However, there's no way to verify
+securely that a particular message came from a particular local user;
+how do you know the recipient is telling you the truth about the
+contents of the message? With QUEUE_EXTRA it'd be possible to record a
+one-way hash of each outgoing message, but a user who wants to send
+``bad'' mail can avoid qmail entirely.
+
+I originally decided on security grounds not to put qmail advertisements
+into SMTP responses: advertisements often act as version identifiers.
+But this problem went away when I found a stable qmail URL.
+
+As qmail grows in popularity, the mere knowledge that rcpthosts is so
+easily available will deter people from setting up unauthorized MXs.
+(I've never seen an unauthorized MX, but I can imagine that it would be
+rather annoying.) Note that, unlike the bat book checkcompat() kludge,
+rcpthosts doesn't interfere with mailing lists.
+
+qmail-start doesn't bother with tty dissociation. On some old machines
+this means that random people can send tty signals to the qmail daemons.
+That's a security flaw in the job control subsystem, not in qmail.
+
+The resolver library isn't too bloated (before 4.9.4, at least), but it
+uses stdio, which _is_ bloated. Reading /etc/resolv.conf costs lots of
+memory in each qmail-remote process. So it's tempting to incorporate a
+smaller resolver library into qmail. (Bonus: I'd avoid system-specific
+problems with old resolvers.) The problem is that I'd then be writing a
+fundamentally insecure library. I'd no longer be able to blame the BIND
+authors and vendors for the fact that attackers can easily use DNS to
+steal mail. Possible solution: replace dns.c with something that passes
+requests (reliably!) to a local daemon; call the original resolver
+library from that daemon.
+
+NFS is the primary enemy of security partitioning under UNIX. Here's the
+story. Sun knew from the start that NFS was completely insecure. It
+tried to hide that fact by disallowing root access over NFS. Intruders
+nevertheless broke into system after system, first obtaining bin access
+and then obtaining root access. Various people thus decided to compound
+Sun's error and build a wall between root and all other users: if all
+system files are owned by root, and if there are no security holes other
+than NFS, someone who breaks in via NFS won't be able to wipe out the
+operating system---he'll merely be able to wipe out all user files. This
+clueless policy means that, for example, all the qmail users have to be
+replaced by root. See what I mean by ``enemy''? ... Basic NFS comments:
+Aside from the cryptographic problem of having hosts communicate
+securely, it's obvious that there's an administrative problem of mapping
+client uids to server uids. If a host is secure and under your control,
+you shouldn't have to map anything. If a host is under someone else's
+control, you'll want to map his uids to one local account; it's his
+client's job to decide which of his users get to talk NFS in the first
+place. Sun's original map---root to nobody, everyone else left alone---
+is, as far as I can tell, always wrong.
+
+
+2. Injecting mail locally (qmail-inject, sendmail-clone)
+
+RFC 822 section 3.4.9 prohibits certain visual effects in headers.
+qmail-inject doesn't waste the time to enforce this absurd restriction.
+If you will suffer from someone sending you ``flash mail,'' go find a
+better mail reader.
+
+qmail-inject's ``Cc: recipient list not shown: ;'' successfully stops
+sendmail from adding Apparently-To. Unfortunately, old versions of
+sendmail will append a host name. This wasn't fixed until sendmail 8.7.
+How many years has it been since RFC 822 came out?
+
+sendmail discards duplicate addresses. This has probably resulted in
+more lost and stolen mail over the years than the entire Chicago branch
+of the United States Postal Service. The qmail system delivers messages
+exactly as it's told to do. Along the same lines: qmail-inject is both
+unable and unwilling to support anything like sendmail's (default)
+nometoo option. Of course, a list manager could support nometoo.
+
+There should be a mechanism in qmail-inject that does for envelope
+recipients what Return-Path does for the envelope sender. Then
+qmail-inject -n could print the recipients.
+
+Should qmail-inject bounce messages with no recipients? Should there be
+an option for this? If it stays as is (accept the message), qmail-inject
+could at least avoid invoking qmail-queue.
+
+It is possible to extract non-unique Message-IDs out of qmail-inject.
+Here's how: stop qmail-inject before it gets to the third line of
+main(), then wait until the pids wrap around, then restart qmail-inject
+and blast the message through, then start another qmail-inject with the
+same pid in the same second. I'm not sure how to fix this. (Of course,
+the user could just type in his own non-unique Message-IDs.)
+
+The bat book says: ``Rules that hide hosts in a domain should be applied
+only to sender addresses.'' Recipient masquerading works fine with
+qmail. None of sendmail's pitfalls apply, basically because qmail has a
+straight paper path.
+
+I expect to receive some pressure to make up for the failings of MUA
+writers who don't understand the concept of reliability. (``Like, duh,
+you mean I was supposed to check the sendmail exit code?'')
+
+
+3. Receiving mail from the network (tcp-env, qmail-smtpd)
+
+RFC 1123 requires VRFY support, but says that it's okay if an
+implementation can be configured to not allow VRFY. qmail-smtpd doesn't
+allow VRFY. If you desperately want your SMTP server (i.e., inetd) to
+provide useful information for VRFY, just compile and install sendmail.
+Were the RFC 1123 writers aware of the as-if principle of interface
+specification? ... They say that VRFY and EXPN are important for
+tracking down cross-host mailing list loops. Catch up to the 1990s,
+guys: with Delivered-To, mailing list loops do absolutely no damage,
+_and_ one of the list administrators gets a bounce that shows exactly
+how the loop occurred. Solve the problem, not the symptom. ... There's a
+vastly superior alternative to EXPN. Hint: finger postmaster@ai.mit.edu.
+
+Should dns.c make special allowances for 127.0.0.1/localhost?
+
+badmailfrom (like 8BITMIME) is a waste of code space.
+
+
+4. Adding messages to the queue (qmail-queue)
+
+Should qmail-queue try to make sure enough disk space is free in
+advance? When qmail-queue is invoked by qmail-local or (with ESMTP)
+qmail-smtpd or qmail-qmtpd, it could be told a size in advance. I wish
+UNIX had an atomic allocate-disk-space routine... 
+
+The qmail.h interface (reflecting the qmail-queue interface, which in
+turn reflects the current queue file structure) is constitutionally
+incapable of handling an address that contains a 0 byte. I can't imagine
+that this will be a problem.
+
+Should qmail-queue not bother queueing a message with no recipients?
+
+
+5. Handling queued mail (qmail-send, qmail-clean)
+
+The queue directory must be local. Mounting it over NFS is extremely
+dangerous---not that this stops people from running sendmail that way!
+Perhaps it is worth putting together a diskless-host qmail package with
+just qmail-inject and an SMTP client in place of qmail-queue. Sending
+mail to the server via SMTP is of course vastly better than trying to do
+anything over NFS. If the NFS server is up but the mail server is down,
+users will just have to wait.
+
+Queue reliability demands that single-byte writes be atomic. This is
+true for a fixed-block filesystem such as UFS, and for a logging
+filesystem such as LFS.
+
+qmail-send uses 8 bytes of memory per queued message. Double that for
+reallocation. (Fix: use a small forest of heaps; i.e., keep several
+prioqs.) Double again for buddy malloc()s. (Fix: be clever about the
+heap sizes.) 32 bytes is worrisome, but not devastating. Even on my
+disk-heavy memory-light machine, I'd run out of inodes long before
+running out of memory.
+
+Some mail systems organize the queue by host. This is pointless as a
+means of splitting up the queue directory. The real issue is what to do
+when you suddenly find out that a host is up. For local SLIP/PPP links
+you know in advance which hosts need this treatment, so you can handle
+them with virtualdomains and serialmail.
+
+For the old queue structure I implemented recipient list compression:
+if mail goes out to a giant mailing list, and most of the recipients are
+delivered, make a new, compressed, todo list. But this really isn't
+worth the effort: it saves only a tiny bit of CPU time.
+
+qmail-send doesn't have any notions of precedence, priority, fairness,
+importance, etc. It handles the queue in first-seen-first-served order.
+One could put a lot of work into doing something different, but that
+work would be a waste: given the triggering mechanism and qmail's
+deferral strategy, it is exceedingly rare for the queue to contain more
+than one deliverable message at any given moment.
+
+Exception: Even with all the concurrency tricks, qmail-send can end up
+spending a few minutes on a mailing list with thousands of remote
+entries. A user might send a new message to a remote address in the
+meantime. Perhaps qmail-send should limit its time per message to,
+say, thirty recipients. This will require some way to mark recipients
+who were already done on this pass. Possible approach: Maintain two todo
+lists (for both L and R). Always work on the earlier todo list. Move
+deferrals to the other todo list.
+
+qmail-send will never start a pass for a job that it already has. This
+means that, if one delivery takes longer than the retry interval, the
+next pass will be delayed. I implemented the opposite strategy for the
+old queue structure. Some hassles: mark() had to understand how job
+input was buffered; every new delivery had to check whether the same
+mpos in the same message was already being done.
+
+Some things that qmail-send does synchronously: queueing a bounce
+message; doing a cleanup via qmail-clean; classifying and rewriting all
+the addresses in a new message. As usual, making these asynchronous
+would require some housekeeping, but could speed things up a bit.
+(Making bounces asynchronous, without POSIX waitpid(), means that
+wait_pid() has to keep a buffer of previous wait()s. Ugh.)
+
+fsync() is a bottleneck. To make this asynchronous would require gobs of
+dedicated output processes whose only purpose in life is to watch data
+get written to the disk. Inconceivable! (``You keep using that word. I
+do not think that word means what you think it means.'')
+
+On the other hand, I could survive without fsync()ing the local and
+remote and info files as long as I don't unlink todo. This would require
+redefining the queue states. I need to see how much speed can be gained.
+
+Currently qmail-send sends at most one bounce message for each incoming
+message. This means that the sender doesn't get flooded with copies of
+his own message. On the other hand, a single slow address can hold up
+bounces for a bunch of fast addresses. It would be easy to call
+injectbounce() more often. What is the best strategy? This feels like
+the TCP-buffering issue... don't want to pepper the other guy with
+little packets, but do want to get the data across.
+
+qmail-stop implementation: setuid to UID_SEND; kill -TERM -1. Given how
+simple this is, I'm not inclined to set up some tricky locking solution
+where qmail-send records its pid etc. But I just know that, if I provide
+this qmail-stop program, someone will screw himself by making another
+uid the same as UID_SEND, or making UID_SEND be root, or whatever.
+Aargh. Maybe use another named pipe... New solution: Run qmail-start
+under an external service controller---it runs in the foreground now.
+
+Bounce messages could include more statistical information in the first
+paragraph: when I received the message, how many recipients I was
+supposed to handle, how many I successfully dealt with, how many I
+already told you about, how many are still in the queue. Have to
+emphasize that the number of recipients _here_ is perhaps less than the
+number of recipients on the original message.
+
+The readdir() interface hides I/O errors. Lower-level interfaces would
+lead me into a thicket of portability problems. I'm really not sure what
+to do about this. Of course, a hard I/O error means that mail is toast,
+but a soft I/O error shouldn't cause any trouble.
+
+job_open() or pass_dochan() could be paranoid about the same id,channel
+already being open; but, since messdone() is so paranoid, the worst
+possible effect of a bug along these lines would be double delivery.
+
+Mathematical amusement: The optimal retry schedule is essentially,
+though not exactly, independent of the actual distribution of message
+delay times. What really matters is how much cost you assign to retries
+and to particular increases in latency. qmail's current quadratic retry
+schedule says that an hour-long delay in a day-old message is worth the
+same as a ten-minute delay in an hour-old message; this doesn't seem so
+unreasonable.
+
+Insider information: AOL retries their messages every five minutes for
+three days straight. Hmmm.
+
+
+6. Sending mail through the network (qmail-rspawn, qmail-remote)
+
+Are there any hosts, anywhere, whose mailers are bogged down by huge
+messages to multiple recipients at a single host? For typical hosts,
+multiple RCPTs per SMTP aren't an ``efficiency feature''; they're a
+_slowness_ feature. Separate SMTP transactions have much lower latency.
+
+The multiple-RCPT bandwidth gain _might_ be noticeable for a machine
+that sends most messages to a smarthost. It would be easy to have
+qmail-rspawn supply qmail-remote with all the addresses at once, as long
+as qmail-send says when it's about to block... Putting recipients into
+the right order is clearly the UA's job. One multiple-RCPT pitfall is
+that a remote host might not be able to deal with (say) 10 recipients,
+even though RFC 821 says everyone has to be able to handle 100;
+qmail-rspawn would have to notice this and back off. (Not that other
+mailers do. Sometimes I'm amazed Internet mail works at all.)
+
+In the opposite direction: It's tempting to remove the @host part of the
+qmail-remote recip argument. Or at least avoid double-dns_cname.
+
+There are lots of reasons that qmail-rspawn should take a more active
+role in qmail-remote's activities. It should call separate programs to
+do (1) MX lookups, (2) SMTP connections, (3) QMTP connections.
+
+I bounce ambiguous MXs. (An ``ambiguous MX'' is a best-preference MX
+record sending me mail for a host that I don't recognize as local.)
+Automatically treating ambiguous MXs as local is incompatible with my
+design decision to keep local delivery working when the network goes
+down. It puts more faith in DNS than DNS deserves. Much better: Have
+your MX records generated automatically from control/locals.
+
+If I successfully connect to an MX host but it temporarily refuses to
+accept the message, I give up and put the message back into the queue.
+But several documents seem to suggest that I should try further MX
+records. What are they thinking? My approach deals properly with downed
+hosts, hosts that are unreachable through a firewall, and load
+balancing; what else do people use multiple MX records for?
+
+Currently qmail-remote sends data in 1024-byte buffers. Perhaps it
+should try to take account of the MTU.
+
+Perhaps qmail-remote should allocate a fixed amount of DNS/connect()
+time across any number of MXs; this idea is due to Mark Delany.
+
+RFC 821 doesn't say what it means by ``text.'' qmail-remote assumes that
+the server's reply text doesn't contain bare LFs.
+
+
+7. Delivering mail locally (qmail-lspawn, qmail-local)
+
+qmail-local doesn't support comsat. comsat is a pointless abomination.
+Use qbiff if you want that kind of notification.
+
+The getpwnam() interface hides I/O errors. Solution: qmail-pw2u.
+
+
+8. sendmail V8's new features
+
+sendmail-8.8.0/doc/op/op.me includes a list of big improvements of
+sendmail 8.8.0 over sendmail 5.67. Here's how qmail stacks up against
+each of those improvements. (Of course, qmail has its own improvements,
+but that's not the point of this list.)
+
+Connection caching, MX piggybacking: Nope. (Profile. Don't speculate.)
+
+Response to RCPT command is fast: Yup.
+
+IP addresses show up in Received lines: Yup.
+
+Self domain literal is properly handled: Yup.
+
+Different timeouts for QUIT, RCPT, etc.: No, just a single timeout.
+
+Proper <> handling, route-address pruning: Yes, but not configurable.
+
+ESMTP support: Yup. (Server-side, including PIPELINING.)
+
+8-bit clean: Yup. (Including server-side 8BITMIME support; same as
+sendmail with the 8 option.)
+
+Configurable user database: Yup.
+
+BIND support: Yup.
+
+Keyed files: Yes, in qmsmac.
+
+931/1413/Ident/TAP: Yup.
+
+Correct 822 address list parsing: Yup. (Note that sendmail still has
+some major problems with quoting.)
+
+List-owner handling: Yup.
+
+Dynamic header allocation: Yup.
+
+Minimum number of disk blocks: Yes, via tunefs -m.
+
+Checkpointing: Yes, but not configurable---qmail always checkpoints.
+
+Error message configuration: Nope.
+
+GECOS matching: Not directly, but easy to hook in.
+
+Hop limit configuration: No. (qmail's limit is 100 hops. qmail offers
+automatic loop protection much more advanced than hop counting.)
+
+MIME error messages: No. (qmail uses QSBMF error messages, which are
+much easier to parse.)
+
+Forward file path: Yes, via /etc/passwd.
+
+Incoming SMTP configuration: Yes, via inetd or tcpserver.
+
+Privacy options: Yes, but they're not options.
+
+Best-MX mangling: Nope. See section 6 for further discussion.
+
+7-bit mangling: Nope. qmail always uses 8 bits.
+
+Support for up to 20 MX records: Yes, and more. qmail has no limits
+other than memory.
+
+Correct quoting of name-and-address headers: Yup.
+
+VRFY and EXPN now different: Nope. qmail always hides this information.
+
+Multi-word classes, deferred macro expansion, separate envelope/header
+$g processing, separate per-mailer envelope and header processing, new
+command line flags, new configuration lines, new mailer flags, new
+macros: These are sendmail-specific; they wouldn't even make sense for
+qmail. For example, _of course_ qmail handles envelopes and headers
+separately; they're almost entirely different objects!
+
+
+9. Miscellany
+
+sendmail-clone and qsmhook are too bletcherous to be documented. (The
+official replacement for qsmhook is preline, together with the
+qmail-command environment variables.)
+
+I've considered making install atomic, but this is very difficult to do
+right, and pointless if it isn't done right.
+
+RN suggests automatically putting together a reasonable set of lines for
+/etc/passwd. I perceive this as getting into the adduser business, which
+is worrisome: I'll be lynched the first time I screw up somebody's
+passwd file. This should be left to OS-specific installation scripts.
+
+The BSD 4.2 inetd didn't allow a username. I think I can safely forget
+about this. (DS notes that the username works under Ultrix even though
+it's undocumented.)
+
+I should clean up the bput/put choices.
+
+Some of the stralloc_0()s indicate that certain lower-level routines
+should grok stralloc.
+
+RN suggests having qlist smash the case of the incoming host name.
+
+K1J suggests that mailing list subscription managers should have
+a three-way handshake, to prevent person A from subscribing person B to
+a mailing list. qlist doesn't do this, but ezmlm does.
+
+qmail assumes that all times are positive; that pid_t, time_t and ino_t
+fit into unsigned long; that gid_t fits into int; that the character set
+is ASCII; and that all pointers are interchangeable. Do I care?
+
+The bat book justifies sendmail's insane line-splitting mechanism by
+pointing out that it might be useful for ``a 40-character braille
+print-driving program.'' C'mon, guys, is that your best excuse?
+
+qmail's mascot is a dolphin.
diff --git a/TODO b/TODO
new file mode 100644 (file)
index 0000000..6101add
--- /dev/null
+++ b/TODO
@@ -0,0 +1,9 @@
+do some serious coverage testing, preferably under Purify, without alloc slop
+replace INTERNALS and THOUGHTS with a real paper describing qmail
+reorganize qmail-inject to do most rw on characters, not tokens
+allow concurrency over 255
+handle IPv6
+turn qmail-upq into a more serious queue-moving utility
+expand strerr coverage
+rewrite everything from scratch
+maybe allow root to clear tcpto on the fly
diff --git a/UPGRADE b/UPGRADE
new file mode 100644 (file)
index 0000000..c6200ca
--- /dev/null
+++ b/UPGRADE
@@ -0,0 +1,145 @@
+SAVE COPIES OF YOUR OUTGOING MAIL! Like any other piece of software (and
+information generally), the qmail system comes with NO WARRANTY. It's
+much more secure and reliable than sendmail, but that's not saying much.
+
+
+Here's how to upgrade from qmail 1.00 to qmail 1.01. This procedure will
+overwrite the old qmail binaries. Furthermore, it may begin delivering
+messages from the queue before you have had a chance to test it.
+
+
+WARNING: The qmail-start command line has changed.
+
+
+Before starting, compare conf* to your old conf*, and make any necessary
+changes. Do not copy your old conf*; the baseline has changed.
+
+
+How to install:
+
+ 1. Compile the programs:
+       # make
+ 2. Create the formatted man pages, *.0:
+       # make man
+ 3. Inform your users that mail will not be accepted for a few minutes.
+ 4. Disable deliveries by killing your old qmail-send. Wait for it to
+    print ``exiting'' in the log.
+ 5. Disable SMTP service by commenting out the smtp line in inetd.conf;
+    kill -HUP your inetd. (If you are using tcpserver, simply kill -STOP
+    your tcpserver.) Wait for current qmail-smtpd processes to die. (If
+    you are running a QMTP server, disable that too.)
+ 6. Install the new binaries and man pages:
+       # rm /var/qmail/bin/* /var/qmail/man/*/*
+       # make setup
+ 7. Run instcheck to make sure it doesn't print any warnings:
+       # make check
+ 8. Reenable deliveries:
+       # env - PATH="/var/qmail/bin:$PATH" \
+       qmail-start ./Mailbox splogger qmail &
+    Make sure to include the ./ in ./Mailbox.
+ 9. Insert ./Mailbox into the qmail-start line in your boot scripts.
+10. Reenable SMTP service by restoring the smtp line in inetd.conf; kill
+    -HUP your inetd. (If you are using tcpserver, simply kill -CONT your
+    tcpserver. If you are running a QMTP server, reenable that too.)
+
+
+How to test (steps 11-17 can be done before step 10):
+
+11. Look for a
+            qmail: running
+    line in syslog. (The big number is a splogger timestamp.)
+12. Local-local test: Send yourself an empty message. (Replace ``me''
+    with your username. Make sure to include the ``to:'' colon.)
+       % echo to: me | /var/qmail/bin/qmail-inject
+    The message will show up immediately in ~/Mailbox, and syslog will
+    show something like this:
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20345 uid 666
+            qmail: starting delivery 1: msg 53 to local me@domain 
+            qmail: delivery 1: success: did_1+0+0/
+            qmail: end msg 53 
+    (53 is an inode number; 20345 is a process ID; your numbers will
+    probably be different.)
+13. Local-error test: Send a message to a nonexistent local address.
+       % echo to: nonexistent | /var/qmail/bin/qmail-inject
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20351 uid 666
+            qmail: starting delivery 2: msg 53 to local nonexistent@domain
+            qmail: delivery 2: failure: No_such_address.__#5.1.1_/
+           qmail: bounce msg 53 qp 20357
+            qmail: end msg 53 
+            qmail: new msg 54 
+            qmail: info msg 54: bytes 743 from <> qp 20357 uid 666
+            qmail: starting delivery 3: msg 54 to local me@domain
+            qmail: delivery 3: success: did_1+0+0/
+            qmail: end msg 54 
+    You will now have a bounce message in ~/Mailbox.
+14. Local-remote test: Send an empty message to your account on another
+    machine.
+       % echo to: me@wherever | /var/qmail/bin/qmail-inject
+            qmail: new msg 53 
+            qmail: info msg 53: bytes 246 from <me@domain> qp 20372 uid 666
+            qmail: starting delivery 4: msg 53 to remote me@wherever
+            qmail: delivery 4: success: 1.2.3.4_accepted_message./...
+            qmail: end msg 53 
+    There will be a pause between ``starting delivery'' and ``success'';
+    SMTP is slow. Check that the message is in your mailbox on the other
+    machine.
+15. Local-postmaster test: Send mail to postmaster, any capitalization.
+       % echo to: POSTmaster | /var/qmail/bin/qmail-inject
+    Look for the message in ~alias/Mailbox.
+16. Double-bounce test: Send a message with a completely bad envelope.
+       % /var/qmail/bin/qmail-inject -f nonexistent
+       To: unknownuser
+       Subject: testing
+
+       This is a test. This is only a test.
+       %
+    (Use end-of-file, not dot, to end the message.) Look for the double
+    bounce in ~alias/Mailbox.
+17. Group membership test:
+       % cat > ~me/.qmail-groups
+       |groups >> MYGROUPS; exit 0
+       % /var/qmail/bin/qmail-inject me-groups < /dev/null
+       % cat ~me/MYGROUPS
+    MYGROUPS will show your normal gid and nothing else. (Under Solaris,
+    make sure to use /usr/ucb/groups; /usr/bin/groups is broken.)
+18. SMTP server test: Forge some mail locally via SMTP.
+       % telnet 127.0.0.1 25
+       Trying 127.0.0.1...
+       Connected to 127.0.0.1.
+       Escape character is '^]'.
+       220 domain ESMTP
+       helo dude
+       250-domain
+       250-PIPELINING
+       250 8BITMIME
+       mail <me@domain>
+       250 ok
+       rcpt <me@domain>
+       250 ok
+       data
+       354 go ahead
+       Subject: testing
+       
+       This is a test.
+       .
+       250 ok 812345679 qp 12345
+       quit
+       221 domain
+       Connection closed by foreign host.
+       %
+    Look for the message in your mailbox.
+19. Remote-local test: Send yourself some mail from another machine.
+20. Remote-error test: I think you can figure this one out.
+21. UA test: Try sending mail, first to a local account, then to a
+    remote account, with your normal user agent.
+22. Remote-postmaster test: Send mail from another machine to
+    PoStMaStEr@domain. Look for the message in ~alias/Mailbox.
+
+
+That's it! To report success:
+       % ( echo 'First M. Last'; cat `cat SYSDEPS` ) \
+         | mail djb-qst@koobera.math.uic.edu
+Replace First M. Last with your name. If you have questions about qmail,
+contact qmail@pobox.com.
diff --git a/VERSION b/VERSION
new file mode 100644 (file)
index 0000000..88da7bb
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+qmail 1.01
diff --git a/addresses.5 b/addresses.5
new file mode 100644 (file)
index 0000000..1fd4c5b
--- /dev/null
@@ -0,0 +1,260 @@
+.TH addresses 5
+.SH "NAME"
+addresses \- formats for Internet mail addresses
+.SH "INTRODUCTION"
+A
+.B mail address
+is a string of characters containing @.
+
+Every mail address has a
+.B local part
+and a
+.B domain part\fR.
+The domain part is everything after the final @.
+The local part is everything before.
+
+For example, the mail addresses
+
+.EX
+   God@heaven.af.mil
+   @heaven.af.mil
+   @at@@heaven.af.mil
+.EE
+
+all have domain part
+.BR heaven.af.mil .
+The local parts are
+.BR God ,
+empty,
+and
+.BR @at@ .
+
+Some domains have owners.
+It is up to the owner of
+.B heaven.af.mil
+to say how mail messages will be delivered to addresses with domain part
+.BR heaven.af.mil .
+
+The domain part of an address is interpreted without regard to case, so
+
+.EX
+   God@heaven.af.mil
+.br
+   God@HEAVEN.AF.MIL
+.br
+   God@Heaven.AF.Mil
+.EE
+
+all refer to the same domain.
+
+There is one exceptional address that does not contain an @:
+namely, the empty string.
+The empty string cannot be used as a recipient address.
+It can be used as a sender address so that
+the real sender doesn't receive bounces.
+.SH "QMAIL EXTENSIONS"
+The
+.B qmail
+system allows several further types of addresses in mail envelopes.
+
+First, an envelope recipient address without an @ is interpreted as being at
+.IR envnoathost .
+For example, if
+.I envnoathost
+is
+.BR heaven.af.mil ,
+the address
+.B God
+will be rewritten as
+.BR God@heaven.af.mil .
+
+Second, the address
+.B #@[]
+is used as an envelope sender address for double bounces.
+
+Third, envelope sender addresses of the form
+.I pre\fB@\fIhost\fB-@[]
+are used to support variable envelope return paths (VERPs).
+.B qmail-send
+will rewrite 
+.I pre\fB@\fIhost\fB-@[]
+as
+.I prerecip\fB=\fIdomain\fB@\fIhost
+for deliveries to
+.IR recip\fB@\fIdomain .
+Bounces directly from
+.B qmail-send
+will come back to
+.IR pre\fB@\fIhost .
+.SH "CHOOSING MAIL ADDRESSES"
+Here are some suggestions on choosing mail addresses for the Internet.
+
+Do not use non-ASCII characters.
+Under RFC 822 and RFC 821,
+these characters cannot be used in mail headers or in SMTP commands.
+In practice, they are regularly corrupted.
+
+Do not use ASCII control characters.
+NUL is regularly corrupted.
+CR and LF cannot be used in some combinations
+and are corrupted in all.
+None of these characters are usable on business cards.
+
+Avoid spaces and the characters
+
+.EX
+   \\"<>()[],;:
+.EE
+
+These all require quoting in mail headers and in SMTP.
+Many existing mail programs do not handle quoting properly.
+
+Do not use @ in a local part.
+@ requires quoting in mail headers and in SMTP.
+Many programs incorrectly look for the first @,
+rather than the last @,
+to find the domain part of an address.
+
+In a local part,
+do not use two consecutive dots, a dot at the beginning, or a dot at the end.
+Any of these would require quoting in mail headers.
+
+Do not use an empty local part; it cannot appear in SMTP commands.
+
+Avoid local parts longer than 64 characters.
+
+Be wary of uppercase letters in local parts.
+Some mail programs (and users!) will incorrectly convert
+.B God@heaven.af.mil
+to
+.BR god@heaven.af.mil .
+
+Be wary of the following characters:
+
+.EX
+   $&!#~`'^*|{}
+.EE
+
+Some users will not know
+how to feed these characters safely to their mail programs.
+
+In domain names, stick to letters, digits, dash, and dot.
+One popular DNS resolver has,
+under the banner of security,
+recently begun destroying domain names
+that contain certain other characters,
+including underscore.
+Exception: A dotted-decimal IP address in brackets,
+such as
+.BR [127.0.0.1] ,
+identifies a domain owned by whoever owns the host at that IP address,
+and can be used safely.
+
+In a domain name,
+do not use two consecutive dots,
+a dot at the beginning,
+or a dot at the end.
+This means that,
+when a domain name is broken down into components separated by dots,
+there are no empty components.
+
+Always use at least one dot in a domain name.
+If you own the
+.B mil
+domain,
+don't bother using the address
+.BR root@mil ;
+most users will be unable to send messages to that address.
+Same for the root domain.
+
+Avoid domain names longer than 64 characters.
+.SH "ENCODED ADDRESSES IN SMTP COMMANDS"
+RFC 821 defines an encoding of mail addresses in SMTP.
+For example, the addresses
+
+.EX
+   God@heaven.af.mil
+.br
+   a"quote@heaven.af.mil
+.br
+   The Almighty.One@heaven.af.mil
+.EE
+
+could be encoded in RCPT commands as
+
+.EX
+   RCPT TO:<God@heaven.af.mil>
+.br
+   RCPT TO:<a\\"quote@heaven.af.mil>
+.br
+   RCPT TO:<The\\ Almighty.One@heaven.af.mil>
+.EE
+
+There are several restrictions in RFC 821
+on the mail addresses that can be used over SMTP.
+Non-ASCII characters are prohibited.
+The local part must not be empty.
+The domain part must be a sequence of elements separated by dots,
+where each element is either a component,
+a sequence of digits preceded by #,
+or a dotted-decimal IP address surrounded by brackets.
+The only allowable characters in components are
+letters, digits, and dashes.
+Every component must (believe it or not)
+have at least three characters;
+the first character must be a letter;
+the last character must not be a hyphen.
+.SH "ENCODED ADDRESSES IN MAIL HEADERS"
+RFC 822 defines an encoding of mail addresses
+in certain header fields in a mail message.
+For example, the addresses
+
+.EX
+   God@heaven.af.mil
+.br
+   a"quote@heaven.af.mil
+.br
+   The Almighty.One@heaven.af.mil
+.EE
+
+could be encoded in a
+.B To
+field as
+
+.EX
+   To: God@heaven.af.mil,
+.br
+       <@brl.mil:"a\\"quote"@heaven.af.mil>,
+.br
+         "The Almighty".One@heaven.af.mil
+.EE
+
+or perhaps
+
+.EX
+   To: < "God"@heaven .af.mil>,
+.br
+     "a\\"quote" (Who?) @ heaven . af.  mil
+.br
+     , God<"The Almighty.One"@heaven.af.mil>
+.EE
+
+There are several restrictions on the mail addresses that can
+be used in these header fields.
+Non-ASCII characters are prohibited.
+The domain part must be a sequence of elements separated by dots,
+where each element either (1) begins with [ and ends with ]
+or (2) is a nonempty string of printable ASCII characters
+not including any of
+
+.EX
+   \\".<>()[],;:
+.EE
+
+and not including space.
+.SH "SEE ALSO"
+envelopes(5),
+qmail-header(5),
+qmail-inject(8),
+qmail-remote(8),
+qmail-smtpd(8)
diff --git a/alloc.3 b/alloc.3
new file mode 100644 (file)
index 0000000..58b4432
--- /dev/null
+++ b/alloc.3
@@ -0,0 +1,62 @@
+.TH alloc 3
+.SH NAME
+alloc \- allocate memory
+.SH SYNTAX
+.B #include <alloc.h>
+
+char *\fBalloc\fP(\fInew\fR);
+
+void \fBalloc_free\fP(\fIx\fR);
+
+void \fBalloc_re\fP(&\fIx\fR,\fIold\fR,\fInew\fR);
+
+char *\fIx\fR;
+.br
+unsigned int \fIold\fR;
+.br
+unsigned int \fInew\fR;
+.SH DESCRIPTION
+.B alloc
+allocates enough space from the heap for
+.I new
+bytes of data,
+adequately aligned for any data type.
+.I new
+may be 0.
+.B alloc
+returns a pointer to the space.
+If space is not available,
+.B alloc
+returns 0,
+setting
+.B errno
+appropriately.
+
+.B alloc_free
+returns space to the heap.
+
+.B alloc_re
+expands the space allocated to
+.I x
+from
+.I old
+bytes to
+.I new
+bytes.
+It allocates new space,
+copies
+.I old
+bytes from the old space to the new space,
+returns the old space to the heap,
+and changes
+.I x
+to point to the new space.
+It then returns 1.
+If space is not available,
+.B alloc_re
+returns 0,
+leaving the old space alone.
+.SH "SEE ALSO"
+sbrk(2),
+malloc(3),
+error(3)
diff --git a/alloc.c b/alloc.c
new file mode 100644 (file)
index 0000000..c661453
--- /dev/null
+++ b/alloc.c
@@ -0,0 +1,32 @@
+#include "alloc.h"
+#include "error.h"
+extern char *malloc();
+extern void free();
+
+#define ALIGNMENT 16 /* XXX: assuming that this alignment is enough */
+#define SPACE 4096 /* must be multiple of ALIGNMENT */
+
+typedef union { char irrelevant[ALIGNMENT]; double d; } aligned;
+static aligned realspace[SPACE / ALIGNMENT];
+#define space ((char *) realspace)
+static unsigned int avail = SPACE; /* multiple of ALIGNMENT; 0<=avail<=SPACE */
+
+/*@null@*//*@out@*/char *alloc(n)
+unsigned int n;
+{
+  char *x;
+  n = ALIGNMENT + n - (n & (ALIGNMENT - 1)); /* XXX: could overflow */
+  if (n <= avail) { avail -= n; return space + avail; }
+  x = malloc(n);
+  if (!x) errno = error_nomem;
+  return x;
+}
+
+void alloc_free(x)
+char *x;
+{
+  if (x >= space)
+    if (x < space + SPACE)
+      return; /* XXX: assuming that pointers are flat */
+  free(x);
+}
diff --git a/alloc.h b/alloc.h
new file mode 100644 (file)
index 0000000..1b1d893
--- /dev/null
+++ b/alloc.h
@@ -0,0 +1,8 @@
+#ifndef ALLOC_H
+#define ALLOC_H
+
+extern /*@null@*//*@out@*/char *alloc();
+extern void alloc_free();
+extern int alloc_re();
+
+#endif
diff --git a/alloc_re.c b/alloc_re.c
new file mode 100644 (file)
index 0000000..feb8b49
--- /dev/null
@@ -0,0 +1,17 @@
+#include "alloc.h"
+#include "byte.h"
+
+int alloc_re(x,m,n)
+char **x;
+unsigned int m;
+unsigned int n;
+{
+  char *y;
+  y = alloc(n);
+  if (!y) return 0;
+  byte_copy(y,m,*x);
+  alloc_free(*x);
+  *x = y;
+  return 1;
+}
diff --git a/auto-gid.c b/auto-gid.c
new file mode 100644 (file)
index 0000000..774a8c1
--- /dev/null
@@ -0,0 +1,51 @@
+#include <sys/types.h>
+#include <grp.h>
+#include "subfd.h"
+#include "substdio.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf1[256];
+substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1));
+
+void outs(s)
+char *s;
+{
+  if (substdio_puts(&ss1,s) == -1) _exit(111);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *name;
+  char *value;
+  struct group *gr;
+  char strnum[FMT_ULONG];
+
+  name = argv[1];
+  if (!name) _exit(100);
+  value = argv[2];
+  if (!value) _exit(100);
+
+  gr = getgrnam(value);
+  if (!gr) {
+    substdio_puts(subfderr,"fatal: unable to find group ");
+    substdio_puts(subfderr,value);
+    substdio_puts(subfderr,"\n");
+    substdio_flush(subfderr);
+    _exit(111);
+  }
+
+  strnum[fmt_ulong(strnum,(unsigned long) gr->gr_gid)] = 0;
+
+  outs("int ");
+  outs(name);
+  outs(" = ");
+  outs(strnum);
+  outs(";\n");
+  if (substdio_flush(&ss1) == -1) _exit(111);
+  _exit(0);
+}
diff --git a/auto-int.c b/auto-int.c
new file mode 100644 (file)
index 0000000..c138869
--- /dev/null
@@ -0,0 +1,40 @@
+#include "substdio.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf1[256];
+substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1));
+
+void puts(s)
+char *s;
+{
+  if (substdio_puts(&ss1,s) == -1) _exit(111);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *name;
+  char *value;
+  unsigned long num;
+  char strnum[FMT_ULONG];
+
+  name = argv[1];
+  if (!name) _exit(100);
+  value = argv[2];
+  if (!value) _exit(100);
+
+  scan_ulong(value,&num);
+  strnum[fmt_ulong(strnum,num)] = 0;
+
+  puts("int ");
+  puts(name);
+  puts(" = ");
+  puts(strnum);
+  puts(";\n");
+  if (substdio_flush(&ss1) == -1) _exit(111);
+  _exit(0);
+}
diff --git a/auto-int8.c b/auto-int8.c
new file mode 100644 (file)
index 0000000..091978f
--- /dev/null
@@ -0,0 +1,40 @@
+#include "substdio.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf1[256];
+substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1));
+
+void puts(s)
+char *s;
+{
+  if (substdio_puts(&ss1,s) == -1) _exit(111);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *name;
+  char *value;
+  unsigned long num;
+  char strnum[FMT_ULONG];
+
+  name = argv[1];
+  if (!name) _exit(100);
+  value = argv[2];
+  if (!value) _exit(100);
+
+  scan_8long(value,&num);
+  strnum[fmt_ulong(strnum,num)] = 0;
+
+  puts("int ");
+  puts(name);
+  puts(" = ");
+  puts(strnum);
+  puts(";\n");
+  if (substdio_flush(&ss1) == -1) _exit(111);
+  _exit(0);
+}
diff --git a/auto-str.c b/auto-str.c
new file mode 100644 (file)
index 0000000..acc3d60
--- /dev/null
@@ -0,0 +1,44 @@
+#include "substdio.h"
+#include "readwrite.h"
+#include "exit.h"
+
+char buf1[256];
+substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1));
+
+void puts(s)
+char *s;
+{
+  if (substdio_puts(&ss1,s) == -1) _exit(111);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *name;
+  char *value;
+  unsigned char ch;
+  char octal[4];
+
+  name = argv[1];
+  if (!name) _exit(100);
+  value = argv[2];
+  if (!value) _exit(100);
+
+  puts("char ");
+  puts(name);
+  puts("[] = \"\\\n");
+
+  while (ch = *value++) {
+    puts("\\");
+    octal[3] = 0;
+    octal[2] = '0' + (ch & 7); ch >>= 3;
+    octal[1] = '0' + (ch & 7); ch >>= 3;
+    octal[0] = '0' + (ch & 7);
+    puts(octal);
+  }
+
+  puts("\\\n\";\n");
+  if (substdio_flush(&ss1) == -1) _exit(111);
+  _exit(0);
+}
diff --git a/auto-uid.c b/auto-uid.c
new file mode 100644 (file)
index 0000000..326051c
--- /dev/null
@@ -0,0 +1,51 @@
+#include <sys/types.h>
+#include <pwd.h>
+#include "subfd.h"
+#include "substdio.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf1[256];
+substdio ss1 = SUBSTDIO_FDBUF(write,1,buf1,sizeof(buf1));
+
+void outs(s) /* was named puts, but Solaris pwd.h includes stdio.h. dorks. */
+char *s;
+{
+  if (substdio_puts(&ss1,s) == -1) _exit(111);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char *name;
+  char *value;
+  struct passwd *pw;
+  char strnum[FMT_ULONG];
+
+  name = argv[1];
+  if (!name) _exit(100);
+  value = argv[2];
+  if (!value) _exit(100);
+
+  pw = getpwnam(value);
+  if (!pw) {
+    substdio_puts(subfderr,"fatal: unable to find user ");
+    substdio_puts(subfderr,value);
+    substdio_puts(subfderr,"\n");
+    substdio_flush(subfderr);
+    _exit(111);
+  }
+
+  strnum[fmt_ulong(strnum,(unsigned long) pw->pw_uid)] = 0;
+
+  outs("int ");
+  outs(name);
+  outs(" = ");
+  outs(strnum);
+  outs(";\n");
+  if (substdio_flush(&ss1) == -1) _exit(111);
+  _exit(0);
+}
diff --git a/auto_break.h b/auto_break.h
new file mode 100644 (file)
index 0000000..b7f3a63
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_BREAK_H
+#define AUTO_BREAK_H
+
+extern char auto_break[];
+
+#endif
diff --git a/auto_patrn.h b/auto_patrn.h
new file mode 100644 (file)
index 0000000..77cdf1f
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_PATRN_H
+#define AUTO_PATRN_H
+
+extern int auto_patrn;
+
+#endif
diff --git a/auto_qmail.h b/auto_qmail.h
new file mode 100644 (file)
index 0000000..0c56001
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_QMAIL_H
+#define AUTO_QMAIL_H
+
+extern char auto_qmail[];
+
+#endif
diff --git a/auto_spawn.h b/auto_spawn.h
new file mode 100644 (file)
index 0000000..165d988
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_SPAWN_H
+#define AUTO_SPAWN_H
+
+extern int auto_spawn;
+
+#endif
diff --git a/auto_split.h b/auto_split.h
new file mode 100644 (file)
index 0000000..3754129
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_SPLIT_H
+#define AUTO_SPLIT_H
+
+extern int auto_split;
+
+#endif
diff --git a/auto_uids.h b/auto_uids.h
new file mode 100644 (file)
index 0000000..1252ecb
--- /dev/null
@@ -0,0 +1,16 @@
+#ifndef AUTO_UIDS_H
+#define AUTO_UIDS_H
+
+extern int auto_uida;
+extern int auto_uidd;
+extern int auto_uidl;
+extern int auto_uido;
+extern int auto_uidp;
+extern int auto_uidq;
+extern int auto_uidr;
+extern int auto_uids;
+
+extern int auto_gidn;
+extern int auto_gidq;
+
+#endif
diff --git a/auto_usera.h b/auto_usera.h
new file mode 100644 (file)
index 0000000..49d7755
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef AUTO_USERA_H
+#define AUTO_USERA_H
+
+extern char auto_usera[];
+
+#endif
diff --git a/byte.h b/byte.h
new file mode 100644 (file)
index 0000000..de06c69
--- /dev/null
+++ b/byte.h
@@ -0,0 +1,13 @@
+#ifndef BYTE_H
+#define BYTE_H
+
+extern unsigned int byte_chr();
+extern unsigned int byte_rchr();
+extern void byte_copy();
+extern void byte_copyr();
+extern int byte_diff();
+extern void byte_zero();
+
+#define byte_equal(s,n,t) (!byte_diff((s),(n),(t)))
+
+#endif
diff --git a/byte_chr.c b/byte_chr.c
new file mode 100644 (file)
index 0000000..f81dde8
--- /dev/null
@@ -0,0 +1,20 @@
+#include "byte.h"
+
+unsigned int byte_chr(s,n,c)
+char *s;
+register unsigned int n;
+int c;
+{
+  register char ch;
+  register char *t;
+
+  ch = c;
+  t = s;
+  for (;;) {
+    if (!n) break; if (*t == ch) break; ++t; --n;
+    if (!n) break; if (*t == ch) break; ++t; --n;
+    if (!n) break; if (*t == ch) break; ++t; --n;
+    if (!n) break; if (*t == ch) break; ++t; --n;
+  }
+  return t - s;
+}
diff --git a/byte_copy.c b/byte_copy.c
new file mode 100644 (file)
index 0000000..eaad11b
--- /dev/null
@@ -0,0 +1,14 @@
+#include "byte.h"
+
+void byte_copy(to,n,from)
+register char *to;
+register unsigned int n;
+register char *from;
+{
+  for (;;) {
+    if (!n) return; *to++ = *from++; --n;
+    if (!n) return; *to++ = *from++; --n;
+    if (!n) return; *to++ = *from++; --n;
+    if (!n) return; *to++ = *from++; --n;
+  }
+}
diff --git a/byte_cr.c b/byte_cr.c
new file mode 100644 (file)
index 0000000..3e7a1d5
--- /dev/null
+++ b/byte_cr.c
@@ -0,0 +1,16 @@
+#include "byte.h"
+
+void byte_copyr(to,n,from)
+register char *to;
+register unsigned int n;
+register char *from;
+{
+  to += n;
+  from += n;
+  for (;;) {
+    if (!n) return; *--to = *--from; --n;
+    if (!n) return; *--to = *--from; --n;
+    if (!n) return; *--to = *--from; --n;
+    if (!n) return; *--to = *--from; --n;
+  }
+}
diff --git a/byte_diff.c b/byte_diff.c
new file mode 100644 (file)
index 0000000..cdbd760
--- /dev/null
@@ -0,0 +1,16 @@
+#include "byte.h"
+
+int byte_diff(s,n,t)
+register char *s;
+register unsigned int n;
+register char *t;
+{
+  for (;;) {
+    if (!n) return 0; if (*s != *t) break; ++s; ++t; --n;
+    if (!n) return 0; if (*s != *t) break; ++s; ++t; --n;
+    if (!n) return 0; if (*s != *t) break; ++s; ++t; --n;
+    if (!n) return 0; if (*s != *t) break; ++s; ++t; --n;
+  }
+  return ((int)(unsigned int)(unsigned char) *s)
+       - ((int)(unsigned int)(unsigned char) *t);
+}
diff --git a/byte_rchr.c b/byte_rchr.c
new file mode 100644 (file)
index 0000000..476bc22
--- /dev/null
@@ -0,0 +1,23 @@
+#include "byte.h"
+
+unsigned int byte_rchr(s,n,c)
+char *s;
+register unsigned int n;
+int c;
+{
+  register char ch;
+  register char *t;
+  register char *u;
+
+  ch = c;
+  t = s;
+  u = 0;
+  for (;;) {
+    if (!n) break; if (*t == ch) u = t; ++t; --n;
+    if (!n) break; if (*t == ch) u = t; ++t; --n;
+    if (!n) break; if (*t == ch) u = t; ++t; --n;
+    if (!n) break; if (*t == ch) u = t; ++t; --n;
+  }
+  if (!u) u = t;
+  return u - s;
+}
diff --git a/byte_zero.c b/byte_zero.c
new file mode 100644 (file)
index 0000000..92009ba
--- /dev/null
@@ -0,0 +1,13 @@
+#include "byte.h"
+
+void byte_zero(s,n)
+char *s;
+register unsigned int n;
+{
+  for (;;) {
+    if (!n) break; *s++ = 0; --n;
+    if (!n) break; *s++ = 0; --n;
+    if (!n) break; *s++ = 0; --n;
+    if (!n) break; *s++ = 0; --n;
+  }
+}
diff --git a/case.3 b/case.3
new file mode 100644 (file)
index 0000000..58bd724
--- /dev/null
+++ b/case.3
@@ -0,0 +1,100 @@
+.TH case 3
+.SH NAME
+case \- convert ASCII uppercase bytes to lowercase
+.SH SYNTAX
+.B #include <case.h>
+
+void \fBcase_lowers\fP(\fIs\fR);
+.br
+void \fBcase_lowerb\fP(\fIs\fR,\fIlen\fR);
+
+int \fBcase_diffs\fP(\fIs\fR,\fIt\fR);
+.br
+int \fBcase_equals\fP(\fIs\fR,\fIt\fR);
+.br
+int \fBcase_starts\fP(\fIs\fR,\fIt\fR);
+
+int \fBcase_diffb\fP(\fIs\fR,\fIlen\fR,\fIt\fR);
+.br
+int \fBcase_startb\fP(\fIs\fR,\fIlen\fR,\fIt\fR);
+
+char *\fIs\fR;
+.br
+char *\fIt\fR;
+.br
+unsigned int \fIlen\fR;
+.SH DESCRIPTION
+.B case_lowers
+converts each uppercase byte in the string
+.I s
+to lowercase.
+.I s
+must be 0-terminated.
+
+.B case_lowerb
+converts each uppercase byte in the buffer
+.IR s ,
+of length
+.IR len ,
+to lowercase.
+
+.B case_diffs
+lexicographically compares lowercase versions of the strings
+.I s
+and
+.IR t .
+It returns something positive, negative, or zero
+when the first is larger than, smaller than, or equal to the second.
+.I s
+and
+.I t
+must be 0-terminated.
+
+.B case_equals
+means
+.BR !case_diffs .
+
+.B case_starts
+returns 1 if a lowercase version of
+.I s
+starts with a lowercase version of
+.IR t .
+.I s
+and
+.I t
+must be 0-terminated.
+
+.B case_diffb
+lexicographically compares lowercase versions of the buffers
+.I s
+and
+.IR t ,
+each of length
+.IR len .
+It returns something positive, negative, or zero
+when the first is larger than, smaller than, or equal to the second.
+
+.B case_startb
+returns 1 if a lowercase version of the buffer
+.IR s ,
+of length
+.IR len ,
+starts with a lowercase version of the string
+.IR t .
+.I t
+must be 0-terminated.
+
+The
+.B case
+routines
+are ASCII-specific.
+They are suitable for programs that handle
+case-independent networking protocols.
+
+All comparisons are performed on unsigned bytes.
+.SH "SEE ALSO"
+byte_diff(3),
+byte_equal(3),
+str_diff(3),
+str_equal(3),
+str_start(3)
diff --git a/case.h b/case.h
new file mode 100644 (file)
index 0000000..35a2434
--- /dev/null
+++ b/case.h
@@ -0,0 +1,13 @@
+#ifndef CASE_H
+#define CASE_H
+
+extern void case_lowers();
+extern void case_lowerb();
+extern int case_diffs();
+extern int case_diffb();
+extern int case_starts();
+extern int case_startb();
+
+#define case_equals(s,t) (!case_diffs((s),(t)))
+
+#endif
diff --git a/case_diffb.c b/case_diffb.c
new file mode 100644 (file)
index 0000000..9064b8a
--- /dev/null
@@ -0,0 +1,21 @@
+#include "case.h"
+
+int case_diffb(s,len,t)
+register char *s;
+unsigned int len;
+register char *t;
+{
+  register unsigned char x;
+  register unsigned char y;
+
+  while (len > 0) {
+    --len;
+    x = *s++ - 'A';
+    if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
+    y = *t++ - 'A';
+    if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (x != y)
+      return ((int)(unsigned int) x) - ((int)(unsigned int) y);
+  }
+  return 0;
+}
diff --git a/case_diffs.c b/case_diffs.c
new file mode 100644 (file)
index 0000000..212c645
--- /dev/null
@@ -0,0 +1,19 @@
+#include "case.h"
+
+int case_diffs(s,t)
+register char *s;
+register char *t;
+{
+  register unsigned char x;
+  register unsigned char y;
+
+  for (;;) {
+    x = *s++ - 'A';
+    if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
+    y = *t++ - 'A';
+    if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (x != y) break;
+    if (!x) break;
+  }
+  return ((int)(unsigned int) x) - ((int)(unsigned int) y);
+}
diff --git a/case_lowerb.c b/case_lowerb.c
new file mode 100644 (file)
index 0000000..4034c14
--- /dev/null
@@ -0,0 +1,14 @@
+#include "case.h"
+
+void case_lowerb(s,len)
+char *s;
+unsigned int len;
+{
+  unsigned char x;
+  while (len > 0) {
+    --len;
+    x = *s - 'A';
+    if (x <= 'Z' - 'A') *s = x + 'a';
+    ++s;
+  }
+}
diff --git a/case_lowers.c b/case_lowers.c
new file mode 100644 (file)
index 0000000..208b3f5
--- /dev/null
@@ -0,0 +1,12 @@
+#include "case.h"
+
+void case_lowers(s)
+char *s;
+{
+  unsigned char x;
+  while (x = *s) {
+    x -= 'A';
+    if (x <= 'Z' - 'A') *s = x + 'a';
+    ++s;
+  }
+}
diff --git a/case_starts.c b/case_starts.c
new file mode 100644 (file)
index 0000000..2278a0a
--- /dev/null
@@ -0,0 +1,18 @@
+#include "case.h"
+
+int case_starts(s,t)
+register char *s;
+register char *t;
+{
+  register unsigned char x;
+  register unsigned char y;
+
+  for (;;) {
+    x = *s++ - 'A';
+    if (x <= 'Z' - 'A') x += 'a'; else x += 'A';
+    y = *t++ - 'A';
+    if (y <= 'Z' - 'A') y += 'a'; else y += 'A';
+    if (!y) return 1;
+    if (x != y) return 0;
+  }
+}
diff --git a/cdb.3 b/cdb.3
new file mode 100644 (file)
index 0000000..a85b34c
--- /dev/null
+++ b/cdb.3
@@ -0,0 +1,62 @@
+.TH cdb 3
+.SH NAME
+cdb \- read from a constant database
+.SH SYNTAX
+.B #include <cdb.h>
+
+int \fBcdb_seek(\fP\fIfd,key,len,dlen\fR\fB)\fP;
+
+int \fIfd\fR;
+.br
+char *\fIkey\fR;
+.br
+unsigned int \fIlen\fR;
+.br
+uint32 *\fIdlen\fR;
+.SH DESCRIPTION
+.B cdb_seek
+looks up
+.I key
+in a constant database.
+It returns 1 if 
+.I key
+is present,
+0 if 
+.I key
+is not present,
+or \-1 if there was a read error.
+.I key
+is an array of
+.I len
+characters.
+
+.B cdb_seek
+needs an open file descriptor,
+.IR fd ,
+pointing to the database.
+If
+.B cdb_seek
+returns 1,
+it points
+.I fd
+at the beginning of the data portion of the first record
+indexed by
+.IR key ,
+and it stores the data length in
+.IR dlen.
+.B cdb_seek
+does not provide a way to read subsequent records with the same key.
+
+It's fine to do several
+.B cdb_seek
+lookups with the same open file descriptor.
+Beware, however, that two simultaneous
+.B cdb_seek
+lookups can fail horribly;
+separate processes should not share the same database descriptor.
+Furthermore, any updates after the database was opened
+will be invisible.
+It's rarely a good idea for a long-running program
+to hold a database open.
+.SH "SEE ALSO"
+cdbget(1)
diff --git a/cdb.h b/cdb.h
new file mode 100644 (file)
index 0000000..571e5d6
--- /dev/null
+++ b/cdb.h
@@ -0,0 +1,12 @@
+#ifndef CDB_H
+#define CDB_H
+
+#include "uint32.h"
+
+extern uint32 cdb_hash();
+extern uint32 cdb_unpack();
+
+extern int cdb_bread();
+extern int cdb_seek();
+
+#endif
diff --git a/cdb_hash.c b/cdb_hash.c
new file mode 100644 (file)
index 0000000..8238020
--- /dev/null
@@ -0,0 +1,16 @@
+#include "cdb.h"
+
+uint32 cdb_hash(buf,len)
+unsigned char *buf;
+unsigned int len;
+{
+  uint32 h;
+
+  h = 5381;
+  while (len) {
+    --len;
+    h += (h << 5);
+    h ^= (uint32) *buf++;
+  }
+  return h;
+}
diff --git a/cdb_seek.c b/cdb_seek.c
new file mode 100644 (file)
index 0000000..87ab614
--- /dev/null
@@ -0,0 +1,95 @@
+#include <sys/types.h>
+#include <errno.h>
+extern int errno;
+#include "cdb.h"
+
+#ifndef SEEK_SET
+#define SEEK_SET 0
+#endif
+
+int cdb_bread(fd,buf,len)
+int fd;
+char *buf;
+int len;
+{
+  int r;
+  while (len > 0) {
+    do
+      r = read(fd,buf,len);
+    while ((r == -1) && (errno == EINTR));
+    if (r == -1) return -1;
+    if (r == 0) { errno = EIO; return -1; }
+    buf += r;
+    len -= r;
+  }
+  return 0;
+}
+
+static int match(fd,key,len)
+int fd;
+char *key;
+unsigned int len;
+{
+  char buf[32];
+  int n;
+  int i;
+
+  while (len > 0) {
+    n = sizeof(buf);
+    if (n > len) n = len;
+    if (cdb_bread(fd,buf,n) == -1) return -1;
+    for (i = 0;i < n;++i) if (buf[i] != key[i]) return 0;
+    key += n;
+    len -= n;
+  }
+  return 1;
+}
+
+int cdb_seek(fd,key,len,dlen)
+int fd;
+char *key;
+unsigned int len;
+uint32 *dlen;
+{
+  char packbuf[8];
+  uint32 pos;
+  uint32 h;
+  uint32 lenhash;
+  uint32 h2;
+  uint32 loop;
+  uint32 poskd;
+
+  h = cdb_hash(key,len);
+
+  pos = 8 * (h & 255);
+  if (lseek(fd,(off_t) pos,SEEK_SET) == -1) return -1;
+
+  if (cdb_bread(fd,packbuf,8) == -1) return -1;
+
+  pos = cdb_unpack(packbuf);
+  lenhash = cdb_unpack(packbuf + 4);
+
+  if (!lenhash) return 0;
+  h2 = (h >> 8) % lenhash;
+
+  for (loop = 0;loop < lenhash;++loop) {
+    if (lseek(fd,(off_t) (pos + 8 * h2),SEEK_SET) == -1) return -1;
+    if (cdb_bread(fd,packbuf,8) == -1) return -1;
+    poskd = cdb_unpack(packbuf + 4);
+    if (!poskd) return 0;
+    if (cdb_unpack(packbuf) == h) {
+      if (lseek(fd,(off_t) poskd,SEEK_SET) == -1) return -1;
+      if (cdb_bread(fd,packbuf,8) == -1) return -1;
+      if (cdb_unpack(packbuf) == len)
+       switch(match(fd,key,len)) {
+         case -1:
+           return -1;
+         case 1:
+           *dlen = cdb_unpack(packbuf + 4);
+           return 1;
+       }
+    }
+    if (++h2 == lenhash) h2 = 0;
+  }
+  return 0;
+}
diff --git a/cdb_unpack.c b/cdb_unpack.c
new file mode 100644 (file)
index 0000000..c882202
--- /dev/null
@@ -0,0 +1,12 @@
+#include "cdb.h"
+
+uint32 cdb_unpack(buf)
+unsigned char *buf;
+{
+  uint32 num;
+  num = buf[3]; num <<= 8;
+  num += buf[2]; num <<= 8;
+  num += buf[1]; num <<= 8;
+  num += buf[0];
+  return num;
+}
diff --git a/cdbmake.h b/cdbmake.h
new file mode 100644 (file)
index 0000000..883a231
--- /dev/null
+++ b/cdbmake.h
@@ -0,0 +1,35 @@
+#ifndef CDBMAKE_H
+#define CDBMAKE_H
+
+#include "uint32.h"
+
+#define CDBMAKE_HPLIST 1000
+
+struct cdbmake_hp { uint32 h; uint32 p; } ;
+
+struct cdbmake_hplist {
+  struct cdbmake_hp hp[CDBMAKE_HPLIST];
+  struct cdbmake_hplist *next;
+  int num;
+} ;
+
+struct cdbmake {
+  char final[2048];
+  uint32 count[256];
+  uint32 start[256];
+  struct cdbmake_hplist *head;
+  struct cdbmake_hp *split; /* includes space for hash */
+  struct cdbmake_hp *hash;
+  uint32 numentries;
+} ;
+
+extern void cdbmake_pack();
+#define CDBMAKE_HASHSTART ((uint32) 5381)
+extern uint32 cdbmake_hashadd();
+
+extern void cdbmake_init();
+extern int cdbmake_add();
+extern int cdbmake_split();
+extern uint32 cdbmake_throw();
+
+#endif
diff --git a/cdbmake_add.c b/cdbmake_add.c
new file mode 100644 (file)
index 0000000..115f828
--- /dev/null
@@ -0,0 +1,117 @@
+#include "cdbmake.h"
+
+void cdbmake_init(cdbm)
+struct cdbmake *cdbm;
+{
+  cdbm->head = 0;
+  cdbm->split = 0;
+  cdbm->hash = 0;
+  cdbm->numentries = 0;
+}
+
+int cdbmake_add(cdbm,h,p,alloc)
+struct cdbmake *cdbm;
+uint32 h;
+uint32 p;
+char *(*alloc)();
+{
+  struct cdbmake_hplist *head;
+
+  head = cdbm->head;
+  if (!head || (head->num >= CDBMAKE_HPLIST)) {
+    head = (struct cdbmake_hplist *) alloc(sizeof(struct cdbmake_hplist));
+    if (!head) return 0;
+    head->num = 0;
+    head->next = cdbm->head;
+    cdbm->head = head;
+  }
+  head->hp[head->num].h = h;
+  head->hp[head->num].p = p;
+  ++head->num;
+  ++cdbm->numentries;
+  return 1;
+}
+
+int cdbmake_split(cdbm,alloc)
+struct cdbmake *cdbm;
+char *(*alloc)();
+{
+  int i;
+  uint32 u;
+  uint32 memsize;
+  struct cdbmake_hplist *x;
+
+  for (i = 0;i < 256;++i)
+    cdbm->count[i] = 0;
+
+  for (x = cdbm->head;x;x = x->next) {
+    i = x->num;
+    while (i--)
+      ++cdbm->count[255 & x->hp[i].h];
+  }
+
+  memsize = 1;
+  for (i = 0;i < 256;++i) {
+    u = cdbm->count[i] * 2;
+    if (u > memsize)
+      memsize = u;
+  }
+
+  memsize += cdbm->numentries; /* no overflow possible up to now */
+  u = (uint32) 0 - (uint32) 1;
+  u /= sizeof(struct cdbmake_hp);
+  if (memsize > u) return 0;
+
+  cdbm->split = (struct cdbmake_hp *) alloc(memsize * sizeof(struct cdbmake_hp));
+  if (!cdbm->split) return 0;
+
+  cdbm->hash = cdbm->split + cdbm->numentries;
+
+  u = 0;
+  for (i = 0;i < 256;++i) {
+    u += cdbm->count[i]; /* bounded by numentries, so no overflow */
+    cdbm->start[i] = u;
+  }
+
+  for (x = cdbm->head;x;x = x->next) {
+    i = x->num;
+    while (i--)
+      cdbm->split[--cdbm->start[255 & x->hp[i].h]] = x->hp[i];
+  }
+
+  return 1;
+}
+
+uint32 cdbmake_throw(cdbm,pos,b)
+struct cdbmake *cdbm;
+uint32 pos;
+int b;
+{
+  uint32 len;
+  uint32 j;
+  uint32 count;
+  struct cdbmake_hp *hp;
+  uint32 where;
+
+  count = cdbm->count[b];
+
+  len = count + count; /* no overflow possible */
+  cdbmake_pack(cdbm->final + 8 * b,pos);
+  cdbmake_pack(cdbm->final + 8 * b + 4,len);
+
+  if (len) {
+    for (j = 0;j < len;++j)
+      cdbm->hash[j].h = cdbm->hash[j].p = 0;
+
+    hp = cdbm->split + cdbm->start[b];
+    for (j = 0;j < count;++j) {
+      where = (hp->h >> 8) % len;
+      while (cdbm->hash[where].p)
+       if (++where == len)
+         where = 0;
+      cdbm->hash[where] = *hp++;
+    }
+  }
+
+  return len;
+}
diff --git a/cdbmake_hash.c b/cdbmake_hash.c
new file mode 100644 (file)
index 0000000..f9dc3e5
--- /dev/null
@@ -0,0 +1,10 @@
+#include "cdbmake.h"
+
+uint32 cdbmake_hashadd(h,c)
+uint32 h;
+unsigned int c;
+{
+  h += (h << 5);
+  h ^= (uint32) (unsigned char) c;
+  return h;
+}
diff --git a/cdbmake_pack.c b/cdbmake_pack.c
new file mode 100644 (file)
index 0000000..04b5f5b
--- /dev/null
@@ -0,0 +1,11 @@
+#include "cdbmake.h"
+
+void cdbmake_pack(buf,num)
+unsigned char *buf;
+uint32 num;
+{
+  *buf++ = num; num >>= 8;
+  *buf++ = num; num >>= 8;
+  *buf++ = num; num >>= 8;
+  *buf = num;
+}
diff --git a/cdbmss.c b/cdbmss.c
new file mode 100644 (file)
index 0000000..2d8f367
--- /dev/null
+++ b/cdbmss.c
@@ -0,0 +1,65 @@
+#include "readwrite.h"
+#include "seek.h"
+#include "alloc.h"
+#include "cdbmss.h"
+
+int cdbmss_start(c,fd)
+struct cdbmss *c;
+int fd;
+{
+  cdbmake_init(&c->cdbm);
+  c->fd = fd;
+  c->pos = sizeof(c->cdbm.final);
+  substdio_fdbuf(&c->ss,write,fd,c->ssbuf,sizeof(c->ssbuf));
+  return seek_set(fd,(seek_pos) c->pos);
+}
+
+int cdbmss_add(c,key,keylen,data,datalen)
+struct cdbmss *c;
+unsigned char *key;
+unsigned int keylen;
+unsigned char *data;
+unsigned int datalen;
+{
+  uint32 h;
+  int i;
+
+  cdbmake_pack(c->packbuf,(uint32) keylen);
+  cdbmake_pack(c->packbuf + 4,(uint32) datalen);
+  if (substdio_put(&c->ss,c->packbuf,8) == -1) return -1;
+  if (substdio_put(&c->ss,key,keylen) == -1) return -1;
+  if (substdio_put(&c->ss,data,datalen) == -1) return -1;
+
+  h = CDBMAKE_HASHSTART;
+  for (i = 0;i < keylen;++i)
+    h = cdbmake_hashadd(h,(unsigned int) key[i]);
+
+  if (!cdbmake_add(&c->cdbm,h,c->pos,alloc)) return -1;
+
+  c->pos += 8 + keylen + datalen; /* XXX: overflow? */
+  return 0;
+}
+
+int cdbmss_finish(c)
+struct cdbmss *c;
+{
+  int i;
+  uint32 len;
+  uint32 u;
+
+  if (!cdbmake_split(&c->cdbm,alloc)) return -1;
+
+  for (i = 0;i < 256;++i) {
+    len = cdbmake_throw(&c->cdbm,c->pos,i);
+    for (u = 0;u < len;++u) {
+      cdbmake_pack(c->packbuf,c->cdbm.hash[u].h);
+      cdbmake_pack(c->packbuf + 4,c->cdbm.hash[u].p);
+      if (substdio_put(&c->ss,c->packbuf,8) == -1) return -1;
+      c->pos += 8; /* XXX: overflow? */
+    }
+  }
+
+  if (substdio_flush(&c->ss) == -1) return -1;
+  if (seek_begin(c->fd) == -1) return -1;
+  return substdio_putflush(&c->ss,c->cdbm.final,sizeof(c->cdbm.final));
+}
diff --git a/cdbmss.h b/cdbmss.h
new file mode 100644 (file)
index 0000000..5e6bdf4
--- /dev/null
+++ b/cdbmss.h
@@ -0,0 +1,16 @@
+#ifndef CDBMSS_H
+#define CDBMSS_H
+
+#include "cdbmake.h"
+#include "substdio.h"
+
+struct cdbmss {
+  char ssbuf[1024];
+  struct cdbmake cdbm;
+  substdio ss;
+  char packbuf[8];
+  uint32 pos;
+  int fd;
+} ;
+
+#endif
diff --git a/chkshsgr.c b/chkshsgr.c
new file mode 100644 (file)
index 0000000..2bcdade
--- /dev/null
@@ -0,0 +1,9 @@
+#include "exit.h"
+void main()
+{
+ short x[4];
+
+ x[0] = x[1] = 0;
+ if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1);
+ _exit(0);
+}
diff --git a/chkspawn.c b/chkspawn.c
new file mode 100644 (file)
index 0000000..d19259e
--- /dev/null
@@ -0,0 +1,48 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "fmt.h"
+#include "select.h"
+#include "exit.h"
+#include "auto_spawn.h"
+
+char num[FMT_ULONG];
+fd_set fds;
+
+void main()
+{
+  unsigned long hiddenlimit;
+  unsigned long maxnumd;
+  hiddenlimit = sizeof(fds) * 8;
+  maxnumd = (hiddenlimit - 5) / 2;
+  if (auto_spawn < 1) {
+    substdio_puts(subfderr,"Oops. You have set conf-spawn lower than 1.\n");
+    substdio_flush(subfderr);
+    _exit(1);
+  }
+
+  if (auto_spawn > 255) {
+    substdio_puts(subfderr,"Oops. You have set conf-spawn higher than 255.\n");
+    substdio_flush(subfderr);
+    _exit(1);
+  }
+
+  if (auto_spawn > maxnumd) {
+    substdio_puts(subfderr,"Oops. Your system's FD_SET() has a hidden limit of ");
+    substdio_put(subfderr,num,fmt_ulong(num,hiddenlimit));
+    substdio_puts(subfderr," descriptors.\n\
+This means that the qmail daemons could crash if you set the run-time\n\
+concurrency higher than ");
+    substdio_put(subfderr,num,fmt_ulong(num,maxnumd));
+    substdio_puts(subfderr,". So I'm going to insist that the concurrency\n\
+limit in conf-spawn be at most ");
+    substdio_put(subfderr,num,fmt_ulong(num,maxnumd));
+    substdio_puts(subfderr,". Right now it's ");
+    substdio_put(subfderr,num,fmt_ulong(num,(unsigned long) auto_spawn));
+    substdio_puts(subfderr,".\n");
+    substdio_flush(subfderr);
+    _exit(1);
+  }
+  _exit(0);
+}
diff --git a/coe.3 b/coe.3
new file mode 100644 (file)
index 0000000..06591b6
--- /dev/null
+++ b/coe.3
@@ -0,0 +1,25 @@
+.TH coe 3
+.SH NAME
+coe \- set close-on-exec flag for a descriptor
+.SH SYNTAX
+.B #include <coe.h>
+
+int \fBcoe\fP(\fIfd\fR);
+
+int \fIfd\fR;
+.SH DESCRIPTION
+.B coe
+sets the close-on-exec flag for
+file descriptor
+.IR fd ,
+returning 0 if it was successful
+or -1 on error.
+If
+.B coe
+is successful,
+.I fd
+will be closed when the process calls
+.BR execve .
+.SH "SEE ALSO"
+execve(2),
+fcntl(2)
diff --git a/coe.c b/coe.c
new file mode 100644 (file)
index 0000000..d855158
--- /dev/null
+++ b/coe.c
@@ -0,0 +1,8 @@
+#include <fcntl.h>
+#include "coe.h"
+
+int coe(fd)
+int fd;
+{
+  return fcntl(fd,F_SETFD,1);
+}
diff --git a/coe.h b/coe.h
new file mode 100644 (file)
index 0000000..1559bc1
--- /dev/null
+++ b/coe.h
@@ -0,0 +1,6 @@
+#ifndef COE_H
+#define COE_H
+
+extern int coe();
+
+#endif
diff --git a/condredirect.1 b/condredirect.1
new file mode 100644 (file)
index 0000000..7a1292e
--- /dev/null
@@ -0,0 +1,54 @@
+.TH condredirect 1
+.SH NAME
+condredirect \- perhaps redirect mail to another address
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |condredirect
+.I newaddress
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+.B condredirect
+feeds each new mail message to
+.I program
+with the given arguments.
+If
+.I program
+exits 0,
+.B condredirect
+forwards the mail message to
+.IR newaddress ,
+and then exits 99,
+so further commands in
+.B .qmail
+are ignored.
+
+If
+.I program
+exits 111,
+.B condredirect
+exits 111,
+so delivery will be retried later.
+
+If
+.I program
+exits anything else
+(or does not exist),
+.B condredirect
+exits 0,
+so the rest of
+.B .qmail
+will be processed as usual.
+
+Note that
+it is not safe for
+.I program
+to fork a child that
+reads the message in the background.
+.SH "SEE ALSO"
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8)
diff --git a/condredirect.c b/condredirect.c
new file mode 100644 (file)
index 0000000..c6a2e6d
--- /dev/null
@@ -0,0 +1,87 @@
+#include "sig.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "env.h"
+#include "error.h"
+#include "fork.h"
+#include "wait.h"
+#include "seek.h"
+#include "qmail.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "substdio.h"
+
+void die_success() { _exit(0); }
+void die_99() { _exit(99); }
+void die_perm(s) char *s; { substdio_putsflush(subfderr,s); _exit(100); }
+void die_temp(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); }
+void die_nomem() { die_temp("condredirect: fatal: out of memory\n"); }
+
+struct qmail qqt;
+
+int mywrite(fd,buf,len) int fd; char *buf; int len;
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+substdio ssin;
+substdio ssout;
+char inbuf[SUBSTDIO_INSIZE];
+char outbuf[16];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ char *sender;
+ char *dtline;
+ int pid;
+ int wstat;
+
+ if (!argv[1] || !argv[2])
+   die_perm("condredirect: usage: condredirect newaddress program arg ...\n");
+
+ switch(pid = fork())
+  {
+   case -1: die_temp("condredirect: fatal: unable to fork\n");
+   case 0:
+     execvp(argv[2],argv + 2);
+     if (error_temp(errno)) _exit(111);
+     _exit(100);
+  }
+ if (wait_pid(&wstat,pid) != pid)
+   die_perm("condredirect: fatal: internal bug\n");
+ if (wait_crashed(wstat)) die_temp("condredirect: fatal: child crashed\n");
+ switch(wait_exitcode(wstat))
+  {
+   case 0: break;
+   case 111: die_temp("condredirect: fatal: temporary child error\n");
+   default: die_success();
+  }
+
+ if (seek_begin(0) == -1) die_temp("condredirect: fatal: unable to rewind\n");
+ sig_pipeignore();
+
+ sender = env_get("SENDER");
+ if (!sender) die_perm("condredirect: fatal: SENDER not set\n");
+ dtline = env_get("DTLINE");
+ if (!dtline) die_perm("condredirect: fatal: DTLINE not set\n");
+
+ if (qmail_open(&qqt) == -1) die_temp("condredirect: fatal: unable to fork\n");
+ qmail_puts(&qqt,dtline);
+ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+ substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf));
+ if (substdio_copy(&ssout,&ssin) != 0)
+   die_temp("condredirect: fatal: error while reading message\n");
+ substdio_flush(&ssout);
+
+ qmail_from(&qqt,sender);
+ qmail_to(&qqt,argv[1]);
+ switch(qmail_close(&qqt))
+  {
+   case 0: die_99();
+   case QMAIL_TOOLONG: die_perm("condredirect: fatal: permanent qmail-queue error\n");
+   default: die_temp("condredirect: fatal: temporary qmail-queue error\n");
+  }
+}
diff --git a/conf-break b/conf-break
new file mode 100644 (file)
index 0000000..28fd977
--- /dev/null
@@ -0,0 +1,9 @@
+-
+
+This character is the user-ext delimiter. The default delimiter is -,
+meaning that user joe controls joe-anything. Some system administrators
+prefer + or =.
+
+You can override this choice at run time with the qmail-users mechanism.
+
+Multicharacter delimiters are not permitted.
diff --git a/conf-cc b/conf-cc
new file mode 100644 (file)
index 0000000..e58fb9b
--- /dev/null
+++ b/conf-cc
@@ -0,0 +1,3 @@
+cc -O2
+
+This will be used to compile .c files.
diff --git a/conf-groups b/conf-groups
new file mode 100644 (file)
index 0000000..cec0845
--- /dev/null
@@ -0,0 +1,6 @@
+qmail
+nofiles
+
+These are the qmail groups. The second group should not have access to
+any files, but it must be usable for processes; this requirement
+excludes the ``nogroup'' and ``nobody'' groups on many systems.
diff --git a/conf-ld b/conf-ld
new file mode 100644 (file)
index 0000000..a9e796a
--- /dev/null
+++ b/conf-ld
@@ -0,0 +1,3 @@
+cc -s
+
+This will be used to link .o files into an executable.
diff --git a/conf-patrn b/conf-patrn
new file mode 100644 (file)
index 0000000..b0f20b8
--- /dev/null
@@ -0,0 +1,5 @@
+022
+
+These stat bits are not allowed in ~ and ~/.qmail.
+
+Note that ~ftp, ~www, ~uucp, etc. should be owned by root.
diff --git a/conf-qmail b/conf-qmail
new file mode 100644 (file)
index 0000000..1f4cd67
--- /dev/null
@@ -0,0 +1,4 @@
+/var/qmail
+
+This is the qmail home directory. It must be a local directory, not
+shared among machines. This is where qmail queues all mail messages. 
diff --git a/conf-spawn b/conf-spawn
new file mode 100644 (file)
index 0000000..d6a4256
--- /dev/null
@@ -0,0 +1,5 @@
+120
+
+This is a silent concurrency limit. You can't set it above 255. On some
+systems you can't set it above 125. qmail will refuse to compile if the
+limit is too high.
diff --git a/conf-split b/conf-split
new file mode 100644 (file)
index 0000000..dc6aaf1
--- /dev/null
@@ -0,0 +1,3 @@
+23
+
+This is the queue subdirectory split.
diff --git a/conf-users b/conf-users
new file mode 100644 (file)
index 0000000..9bb35df
--- /dev/null
@@ -0,0 +1,15 @@
+alias
+qmaild
+qmaill
+root
+qmailp
+qmailq
+qmailr
+qmails
+
+The qmail system is heavily partitioned for security; it does almost
+nothing as root.
+
+The first eight lines of this file are the alias user, the daemon user,
+the log user, the owner of miscellaneous files such as binaries, the
+passwd user, the queue user, the remote user, and the send user.
diff --git a/constmap.c b/constmap.c
new file mode 100644 (file)
index 0000000..9a28191
--- /dev/null
@@ -0,0 +1,123 @@
+#include "constmap.h"
+#include "alloc.h"
+#include "case.h"
+
+static constmap_hash hash(s,len)
+char *s;
+int len;
+{
+ unsigned char ch;
+ constmap_hash h;
+ h = 5381;
+ while (len > 0)
+  {
+   ch = *s++ - 'A';
+   if (ch <= 'Z' - 'A') ch += 'a' - 'A';
+   h = ((h << 5) + h) ^ ch;
+   --len;
+  }
+ return h;
+}
+
+char *constmap(cm,s,len)
+struct constmap *cm;
+char *s;
+int len;
+{
+ constmap_hash h;
+ int pos;
+ h = hash(s,len);
+ pos = cm->first[h & cm->mask];
+ while (pos != -1)
+  {
+   if (h == cm->hash[pos])
+     if (len == cm->inputlen[pos])
+       if (!case_diffb(cm->input[pos],len,s))
+        return cm->input[pos] + cm->inputlen[pos] + 1;
+   pos = cm->next[pos];
+  }
+ return 0;
+}
+
+int constmap_init(cm,s,len,flagcolon)
+struct constmap *cm;
+char *s;
+int len;
+int flagcolon;
+{
+ int i;
+ int j;
+ int k;
+ int pos;
+ constmap_hash h;
+
+ cm->num = 0;
+ for (j = 0;j < len;++j) if (!s[j]) ++cm->num;
+
+ h = 64;
+ while (h && (h < cm->num)) h += h;
+ cm->mask = h - 1;
+
+ cm->first = (int *) alloc(sizeof(int) * h);
+ if (cm->first)
+  {
+   cm->input = (char **) alloc(sizeof(char *) * cm->num);
+   if (cm->input)
+    {
+     cm->inputlen = (int *) alloc(sizeof(int) * cm->num);
+     if (cm->inputlen)
+      {
+       cm->hash = (constmap_hash *) alloc(sizeof(constmap_hash) * cm->num);
+       if (cm->hash)
+       {
+        cm->next = (int *) alloc(sizeof(int) * cm->num);
+        if (cm->next)
+         {
+          for (h = 0;h <= cm->mask;++h)
+            cm->first[h] = -1;
+           pos = 0;
+           i = 0;
+           for (j = 0;j < len;++j)
+             if (!s[j])
+              {
+              k = j - i;
+              if (flagcolon)
+               {
+                for (k = i;k < j;++k)
+                  if (s[k] == ':')
+                    break;
+                if (k >= j) { i = j + 1; continue; }
+                k -= i;
+               }
+               cm->input[pos] = s + i;
+               cm->inputlen[pos] = k;
+               h = hash(s + i,k);
+               cm->hash[pos] = h;
+               h &= cm->mask;
+               cm->next[pos] = cm->first[h];
+               cm->first[h] = pos;
+               ++pos;
+               i = j + 1;
+              }
+           return 1;
+         }
+        alloc_free(cm->hash);
+       }
+       alloc_free(cm->inputlen);
+      }
+     alloc_free(cm->input);
+    }
+   alloc_free(cm->first);
+  }
+ return 0;
+}
+
+void constmap_free(cm)
+struct constmap *cm;
+{
+ alloc_free(cm->next);
+ alloc_free(cm->hash);
+ alloc_free(cm->inputlen);
+ alloc_free(cm->input);
+ alloc_free(cm->first);
+}
diff --git a/constmap.h b/constmap.h
new file mode 100644 (file)
index 0000000..17fb1cb
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef CONSTMAP_H
+#define CONSTMAP_H
+
+typedef unsigned long constmap_hash;
+
+struct constmap
+ {
+  int num;
+  constmap_hash mask;
+  constmap_hash *hash;
+  int *first;
+  int *next;
+  char **input;
+  int *inputlen;
+ }
+;
+
+extern int constmap_init();
+extern void constmap_free();
+extern char *constmap();
+
+#endif
diff --git a/control.c b/control.c
new file mode 100644 (file)
index 0000000..4cc80cf
--- /dev/null
+++ b/control.c
@@ -0,0 +1,129 @@
+#include "readwrite.h"
+#include "open.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "error.h"
+#include "control.h"
+#include "alloc.h"
+#include "scan.h"
+
+static char inbuf[64];
+static stralloc line = {0};
+static stralloc me = {0};
+static int meok = 0;
+
+static void striptrailingwhitespace(sa)
+stralloc *sa;
+{
+ while (sa->len > 0)
+   switch(sa->s[sa->len - 1])
+    {
+     case '\n': case ' ': case '\t':
+       --sa->len;
+       break;
+     default:
+       return;
+    }
+}
+
+int control_init()
+{
+ int r;
+ r = control_readline(&me,"control/me");
+ if (r == 1) meok = 1;
+ return r;
+}
+
+int control_rldef(sa,fn,flagme,def)
+stralloc *sa;
+char *fn;
+int flagme;
+char *def;
+{
+ int r;
+ r = control_readline(sa,fn);
+ if (r) return r;
+ if (flagme) if (meok) return stralloc_copy(sa,&me) ? 1 : -1;
+ if (def) return stralloc_copys(sa,def) ? 1 : -1;
+ return r;
+}
+
+int control_readline(sa,fn)
+stralloc *sa;
+char *fn;
+{
+ substdio ss;
+ int fd;
+ int match;
+
+ fd = open_read(fn);
+ if (fd == -1) { if (errno == error_noent) return 0; return -1; }
+ substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf));
+
+ if (getln(&ss,sa,&match,'\n') == -1) { close(fd); return -1; }
+
+ striptrailingwhitespace(sa);
+ close(fd);
+ return 1;
+}
+
+int control_readint(i,fn)
+int *i;
+char *fn;
+{
+ unsigned long u;
+ switch(control_readline(&line,fn))
+  {
+   case 0: return 0;
+   case -1: return -1;
+  }
+ if (scan_nbblong(line.s,line.len,10,0,&u) == 0) return 0;
+ *i = u;
+ return 1;
+}
+
+int control_readfile(sa,fn,flagme)
+stralloc *sa;
+char *fn;
+int flagme;
+{
+ substdio ss;
+ int fd;
+ int match;
+
+ if (!stralloc_copys(sa,"")) return -1;
+
+ fd = open_read(fn);
+ if (fd == -1) 
+  {
+   if (errno == error_noent)
+    {
+     if (flagme && meok)
+      {
+       if (!stralloc_copy(sa,&me)) return -1;
+       if (!stralloc_0(sa)) return -1;
+       return 1;
+      }
+     return 0;
+    }
+   return -1;
+  }
+
+ substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf));
+
+ for (;;)
+  {
+   if (getln(&ss,&line,&match,'\n') == -1) break;
+   if (!match && !line.len) { close(fd); return 1; }
+   striptrailingwhitespace(&line);
+   if (!stralloc_0(&line)) break;
+   if (line.s[0])
+     if (line.s[0] != '#')
+       if (!stralloc_cat(sa,&line)) break;
+   if (!match) { close(fd); return 1; }
+  }
+ close(fd);
+ return -1;
+}
diff --git a/control.h b/control.h
new file mode 100644 (file)
index 0000000..7cba89b
--- /dev/null
+++ b/control.h
@@ -0,0 +1,10 @@
+#ifndef CONTROL_H
+#define CONTROL_H
+
+extern int control_init();
+extern int control_readline();
+extern int control_rldef();
+extern int control_readint();
+extern int control_readfile();
+
+#endif
diff --git a/date822fmt.c b/date822fmt.c
new file mode 100644 (file)
index 0000000..7674bd1
--- /dev/null
@@ -0,0 +1,29 @@
+#include "datetime.h"
+#include "fmt.h"
+#include "date822fmt.h"
+
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+unsigned int date822fmt(s,dt)
+char *s;
+struct datetime *dt;
+{
+  unsigned int i;
+  unsigned int len;
+  len = 0;
+  i = fmt_uint(s,dt->mday); len += i; if (s) s += i;
+  i = fmt_str(s," "); len += i; if (s) s += i;
+  i = fmt_str(s,montab[dt->mon]); len += i; if (s) s += i;
+  i = fmt_str(s," "); len += i; if (s) s += i;
+  i = fmt_uint(s,dt->year + 1900); len += i; if (s) s += i;
+  i = fmt_str(s," "); len += i; if (s) s += i;
+  i = fmt_uint0(s,dt->hour,2); len += i; if (s) s += i;
+  i = fmt_str(s,":"); len += i; if (s) s += i;
+  i = fmt_uint0(s,dt->min,2); len += i; if (s) s += i;
+  i = fmt_str(s,":"); len += i; if (s) s += i;
+  i = fmt_uint0(s,dt->sec,2); len += i; if (s) s += i;
+  i = fmt_str(s," -0000\n"); len += i; if (s) s += i;
+  return len;
+}
diff --git a/date822fmt.h b/date822fmt.h
new file mode 100644 (file)
index 0000000..1848e5a
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef DATE822FMT_H
+#define DATE822FMT_H
+
+extern unsigned int date822fmt();
+#define DATE822FMT 60
+
+#endif
diff --git a/datemail.sh b/datemail.sh
new file mode 100644 (file)
index 0000000..fd28f46
--- /dev/null
@@ -0,0 +1 @@
+exec QMAIL/bin/predate QMAIL/bin/sendmail ${1+"$@"}
diff --git a/datetime.3 b/datetime.3
new file mode 100644 (file)
index 0000000..33a623f
--- /dev/null
@@ -0,0 +1,73 @@
+.TH datetime 3
+.SH NAME
+datetime \- convert between TAI labels and seconds
+.SH SYNTAX
+.B #include <datetime.h>
+
+void \fBdatetime_tai\fP(&\fIdt\fR,\fIt\fR);
+
+datetime_sec \fBdatetime_untai\fP(&\fIdt\fR);
+
+struct datetime \fIdt\fR;
+.br
+datetime_sec \fIt\fR;
+.SH DESCRIPTION
+International Atomic Time, TAI,
+is the fundamental unit for time measurements.
+TAI has one label for every second of real time,
+without complications such as leap seconds.
+
+A
+struct datetime
+variable,
+such as
+.IR dt ,
+stores a TAI label.
+.I dt\fB.year
+is the year number minus 1900;
+.I dt\fB.mon
+is the month number, from 0 (January) through 11 (December);
+.I dt\fB.mday
+is the day of the month, from 1 through 31;
+.I dt\fB.hour
+is the hour, from 0 through 23;
+.I dt\fB.min
+is the minute, from 0 through 59;
+.I dt\fB.sec
+is the second, from 0 through 59;
+.I dt\fB.wday
+is the day of the week, from 0 (Sunday) through 6 (Saturday);
+.I dt\fB.yday
+is the day of the year, from 0 through 365.
+
+The
+.B datetime
+library supports more convenient TAI manipulation with
+the datetime_sec type.
+A datetime_sec value, such as
+.IR t ,
+is an integer referring to the
+.IR t th
+second after the beginning of 1970 TAI.
+The first second of 1970 TAI was 0;
+the next second was 1;
+the last second of 1969 TAI was -1.
+The difference between two datetime_sec values is a number
+of real-time seconds.
+
+.B datetime_tai
+converts a datetime_sec to a TAI label.
+
+.B datetime_untai
+reads a TAI label
+(specifically
+.IR dt\fB.year ,
+.IR dt\fB.mon ,
+.IR dt\fB.mday ,
+.IR dt\fB.hour ,
+.IR dt\fB.min ,
+and
+.IR dt\fB.sec )
+and returns a datetime_sec.
+.SH "SEE ALSO"
+now(3)
diff --git a/datetime.c b/datetime.c
new file mode 100644 (file)
index 0000000..7b8a803
--- /dev/null
@@ -0,0 +1,55 @@
+/* 19950925 */
+#include "datetime.h"
+
+void datetime_tai(dt,t)
+struct datetime *dt;
+datetime_sec t;
+{
+  int day;
+  int tod;
+  int year;
+  int yday;
+  int wday;
+  int mon;
+  tod = t % 86400;
+  day = t / 86400;
+  if (tod < 0) { tod += 86400; --day; }
+  dt->hour = tod / 3600;
+  tod %= 3600;
+  dt->min = tod / 60;
+  dt->sec = tod % 60;
+  wday = (day + 4) % 7; if (wday < 0) wday += 7;
+  dt->wday = wday;
+  day -= 11017;
+  /* day 0 is march 1, 2000 */
+  year = 5 + day / 146097;
+  day = day % 146097; if (day < 0) { day += 146097; --year; }
+  /* from now on, day is nonnegative */
+  year *= 4;
+  if (day == 146096) { year += 3; day = 36524; }
+  else { year += day / 36524; day %= 36524; }
+  year *= 25;
+  year += day / 1461;
+  day %= 1461;
+  year *= 4;
+  yday = (day < 306);
+  if (day == 1460) { year += 3; day = 365; }
+  else { year += day / 365; day %= 365; }
+  yday += day;
+  day *= 10;
+  mon = (day + 5) / 306;
+  day = day + 5 - 306 * mon;
+  day /= 10;
+  if (mon >= 10) { yday -= 306; ++year; mon -= 10; }
+  else { yday += 59; mon += 2; }
+  dt->yday = yday;
+  dt->year = year - 1900;
+  dt->mon = mon;
+  dt->mday = day + 1;
+}
diff --git a/datetime.h b/datetime.h
new file mode 100644 (file)
index 0000000..cde2a9b
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef DATETIME_H
+#define DATETIME_H
+
+struct datetime {
+  int hour;
+  int min;
+  int sec;
+  int wday;
+  int mday;
+  int yday;
+  int mon;
+  int year;
+} ;
+
+typedef long datetime_sec;
+
+extern void datetime_tai();
+extern datetime_sec datetime_untai();
+
+#endif
diff --git a/datetime_un.c b/datetime_un.c
new file mode 100644 (file)
index 0000000..b5048f8
--- /dev/null
@@ -0,0 +1,35 @@
+#include "datetime.h"
+
+/* roughly 100x faster than mktime() */
+datetime_sec datetime_untai(dt)
+struct datetime *dt;
+{
+  int year;
+  int day;
+  int mon;
+  year = dt->year + 1900;
+  mon = dt->mon;
+  if (mon >= 2) { mon -= 2; }
+  else { mon += 10; --year; }
+  day = (dt->mday - 1) * 10 + 5 + 306 * mon;
+  day /= 10;
+  if (day == 365) { year -= 3; day = 1460; }
+  else { day += 365 * (year % 4); }
+  year /= 4;
+  day += 1461 * (year % 25);
+  year /= 25;
+  if (day == 36524) { year -= 3; day = 146096; }
+  else { day += 36524 * (year % 4); }
+  year /= 4;
+  day += 146097 * (year - 5);
+  day += 11017;
+  return ((day * 24 + dt->hour) * 60 + dt->min) * 60 + dt->sec;
+}
diff --git a/direntry.3 b/direntry.3
new file mode 100644 (file)
index 0000000..8928fbb
--- /dev/null
@@ -0,0 +1,36 @@
+.TH direntry 3
+.SH NAME
+direntry \- read directory entries
+.SH SYNTAX
+.B #include <direntry.h>
+
+DIR *\fBopendir\fP(\fIfn\fR);
+
+struct direntry *\fBreaddir\fP(\fIdir\fP);
+
+void \fBclosedir\fP(\fIdir\fP);
+
+DIR *\fIdir\fR;
+.br
+char *\fIfn\fR;
+.SH DESCRIPTION
+The point of
+.B direntry.h
+is to provide a uniform interface to BSD's
+.B sys/dir.h
+and POSIX's
+.BR dirent.h .
+
+The
+.B readdir
+interface is highly unsatisfactory.
+It does not distinguish between I/O errors and end-of-directory.
+It uses
+.BR malloc .
+The return type for
+.B closedir
+varies: some implementations return the
+.B close
+return value.
+.SH "SEE ALSO"
+readdir(3)
diff --git a/direntry.h1 b/direntry.h1
new file mode 100644 (file)
index 0000000..f737676
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DIRENTRY_H
+#define DIRENTRY_H
+
+#include <sys/types.h>
+#include <sys/dir.h>
+#define direntry struct direct
+
+#endif
diff --git a/direntry.h2 b/direntry.h2
new file mode 100644 (file)
index 0000000..0302ebe
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef DIRENTRY_H
+#define DIRENTRY_H
+
+#include <sys/types.h>
+#include <dirent.h>
+#define direntry struct dirent
+
+#endif
diff --git a/dns.c b/dns.c
new file mode 100644 (file)
index 0000000..bbb9770
--- /dev/null
+++ b/dns.c
@@ -0,0 +1,402 @@
+#include <stdio.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <errno.h>
+extern int res_query();
+extern int res_search();
+extern int errno;
+extern int h_errno;
+#include "ip.h"
+#include "ipalloc.h"
+#include "fmt.h"
+#include "alloc.h"
+#include "str.h"
+#include "stralloc.h"
+#include "dns.h"
+#include "case.h"
+
+static unsigned short getshort(c) unsigned char *c;
+{ unsigned short u; u = c[0]; return (u << 8) + c[1]; }
+
+static union { HEADER hdr; unsigned char buf[PACKETSZ]; } response;
+static int responselen;
+static unsigned char *responseend;
+static unsigned char *responsepos;
+
+static int numanswers;
+static char name[MAXDNAME];
+static struct ip_address ip;
+unsigned short pref;
+
+static stralloc glue = {0};
+
+static int (*lookup)() = res_query;
+
+static int resolve(domain,type)
+stralloc *domain;
+int type;
+{
+ int n;
+ int i;
+
+ errno = 0;
+ if (!stralloc_copy(&glue,domain)) return DNS_MEM;
+ if (!stralloc_0(&glue)) return DNS_MEM;
+ responselen = lookup(glue.s,C_IN,type,response.buf,sizeof(response));
+ if (responselen <= 0)
+  {
+   if (errno == ECONNREFUSED) return DNS_SOFT;
+   if (h_errno == TRY_AGAIN) return DNS_SOFT;
+   return DNS_HARD;
+  }
+ if (responselen >= sizeof(response))
+   responselen = sizeof(response);
+ responseend = response.buf + responselen;
+ responsepos = response.buf + sizeof(HEADER);
+ n = ntohs(response.hdr.qdcount);
+ while (n-- > 0)
+  {
+   i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
+   if (i < 0) return DNS_SOFT;
+   responsepos += i;
+   i = responseend - responsepos;
+   if (i < QFIXEDSZ) return DNS_SOFT;
+   responsepos += QFIXEDSZ;
+  }
+ numanswers = ntohs(response.hdr.ancount);
+ return 0;
+}
+
+static int findname(wanttype)
+int wanttype;
+{
+ unsigned short rrtype;
+ unsigned short rrdlen;
+ int i;
+
+ if (numanswers <= 0) return 2;
+ --numanswers;
+ if (responsepos == responseend) return DNS_SOFT;
+
+ i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
+ if (i < 0) return DNS_SOFT;
+ responsepos += i;
+
+ i = responseend - responsepos;
+ if (i < 4 + 3 * 2) return DNS_SOFT;
+   
+ rrtype = getshort(responsepos);
+ rrdlen = getshort(responsepos + 8);
+ responsepos += 10;
+
+ if (rrtype == wanttype)
+  {
+   if (dn_expand(response.buf,responseend,responsepos,name,MAXDNAME) < 0)
+     return DNS_SOFT;
+   responsepos += rrdlen;
+   return 1;
+  }
+   
+ responsepos += rrdlen;
+ return 0;
+}
+
+static int findip(wanttype)
+int wanttype;
+{
+ unsigned short rrtype;
+ unsigned short rrdlen;
+ int i;
+
+ if (numanswers <= 0) return 2;
+ --numanswers;
+ if (responsepos == responseend) return DNS_SOFT;
+
+ i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
+ if (i < 0) return DNS_SOFT;
+ responsepos += i;
+
+ i = responseend - responsepos;
+ if (i < 4 + 3 * 2) return DNS_SOFT;
+   
+ rrtype = getshort(responsepos);
+ rrdlen = getshort(responsepos + 8);
+ responsepos += 10;
+
+ if (rrtype == wanttype)
+  {
+   if (rrdlen < 4)
+     return DNS_SOFT;
+   ip.d[0] = responsepos[0];
+   ip.d[1] = responsepos[1];
+   ip.d[2] = responsepos[2];
+   ip.d[3] = responsepos[3];
+   responsepos += rrdlen;
+   return 1;
+  }
+   
+ responsepos += rrdlen;
+ return 0;
+}
+
+static int findmx(wanttype)
+int wanttype;
+{
+ unsigned short rrtype;
+ unsigned short rrdlen;
+ int i;
+
+ if (numanswers <= 0) return 2;
+ --numanswers;
+ if (responsepos == responseend) return DNS_SOFT;
+
+ i = dn_expand(response.buf,responseend,responsepos,name,MAXDNAME);
+ if (i < 0) return DNS_SOFT;
+ responsepos += i;
+
+ i = responseend - responsepos;
+ if (i < 4 + 3 * 2) return DNS_SOFT;
+   
+ rrtype = getshort(responsepos);
+ rrdlen = getshort(responsepos + 8);
+ responsepos += 10;
+
+ if (rrtype == wanttype)
+  {
+   if (rrdlen < 3)
+     return DNS_SOFT;
+   pref = (responsepos[0] << 8) + responsepos[1];
+   if (dn_expand(response.buf,responseend,responsepos + 2,name,MAXDNAME) < 0)
+     return DNS_SOFT;
+   responsepos += rrdlen;
+   return 1;
+  }
+   
+ responsepos += rrdlen;
+ return 0;
+}
+
+void dns_init(flagsearch)
+int flagsearch;
+{
+ res_init();
+ if (flagsearch) lookup = res_search;
+}
+
+int dns_cname(sa)
+stralloc *sa;
+{
+ int r;
+ int loop;
+ for (loop = 0;loop < 10;++loop)
+  {
+   if (!sa->len) return loop;
+   if (sa->s[sa->len - 1] == ']') return loop;
+   if (sa->s[sa->len - 1] == '.') { --sa->len; continue; }
+   switch(resolve(sa,T_ANY))
+    {
+     case DNS_MEM: return DNS_MEM;
+     case DNS_SOFT: return DNS_SOFT;
+     case DNS_HARD: return loop;
+     default:
+       while ((r = findname(T_CNAME)) != 2)
+       {
+        if (r == DNS_SOFT) return DNS_SOFT;
+        if (r == 1)
+         {
+          if (!stralloc_copys(sa,name)) return DNS_MEM;
+          break;
+         }
+       }
+       if (r == 2) return loop;
+    }
+  }
+ return DNS_HARD; /* alias loop */
+}
+
+#define FMT_IAA 40
+
+static int iaafmt(s,ip)
+char *s;
+struct ip_address *ip;
+{
+ unsigned int i;
+ unsigned int len;
+ len = 0;
+ i = fmt_ulong(s,(unsigned long) ip->d[3]); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,(unsigned long) ip->d[2]); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,(unsigned long) ip->d[1]); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,(unsigned long) ip->d[0]); len += i; if (s) s += i;
+ i = fmt_str(s,".in-addr.arpa."); len += i; if (s) s += i;
+ return len;
+}
+
+int dns_ptr(sa,ip)
+stralloc *sa;
+struct ip_address *ip;
+{
+ int r;
+
+ if (!stralloc_ready(sa,iaafmt((char *) 0,ip))) return DNS_MEM;
+ sa->len = iaafmt(sa->s,ip);
+ switch(resolve(sa,T_PTR))
+  {
+   case DNS_MEM: return DNS_MEM;
+   case DNS_SOFT: return DNS_SOFT;
+   case DNS_HARD: return DNS_HARD;
+  }
+ while ((r = findname(T_PTR)) != 2)
+  {
+   if (r == DNS_SOFT) return DNS_SOFT;
+   if (r == 1)
+    {
+     if (!stralloc_copys(sa,name)) return DNS_MEM;
+     return 0;
+    }
+  }
+ return DNS_HARD;
+}
+
+static int dns_ipplus(ia,sa,pref)
+ipalloc *ia;
+stralloc *sa;
+int pref;
+{
+ int r;
+ struct ip_mx ix;
+
+ if (sa->len && (sa->s[0] == '['))
+  {
+   if (!stralloc_copy(&glue,sa)) return DNS_MEM;
+   if (!stralloc_0(&glue)) return DNS_MEM;
+   ix.pref = 0;
+   if (!glue.s[ip_scanbracket(glue.s,&ix.ip)])
+    {
+     if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+     return 0;
+    }
+  }
+
+ switch(resolve(sa,T_A))
+  {
+   case DNS_MEM: return DNS_MEM;
+   case DNS_SOFT: return DNS_SOFT;
+   case DNS_HARD: return DNS_HARD;
+  }
+ while ((r = findip(T_A)) != 2)
+  {
+   ix.ip = ip;
+   ix.pref = pref;
+   if (r == DNS_SOFT) return DNS_SOFT;
+   if (r == 1)
+     if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+  }
+ return 0;
+}
+
+int dns_ip(ia,sa)
+ipalloc *ia;
+stralloc *sa;
+{
+ if (!ipalloc_readyplus(ia,0)) return DNS_MEM;
+ ia->len = 0;
+ return dns_ipplus(ia,sa,0);
+}
+
+int dns_mxip(ia,sa,random)
+ipalloc *ia;
+stralloc *sa;
+unsigned long random;
+{
+ int r;
+ struct mx { stralloc sa; unsigned short p; } *mx;
+ int nummx;
+ int i;
+ int j;
+ int flagsoft;
+
+ if (!ipalloc_readyplus(ia,0)) return DNS_MEM;
+ ia->len = 0;
+
+ if (sa->len && (sa->s[0] == '['))
+  {
+   struct ip_mx ix;
+   if (!stralloc_copy(&glue,sa)) return DNS_MEM;
+   if (!stralloc_0(&glue)) return DNS_MEM;
+   ix.pref = 0;
+   if (!glue.s[ip_scanbracket(glue.s,&ix.ip)])
+    {
+     if (!ipalloc_append(ia,&ix)) return DNS_MEM;
+     return 0;
+    }
+  }
+
+ switch(resolve(sa,T_MX))
+  {
+   case DNS_MEM: return DNS_MEM;
+   case DNS_SOFT: return DNS_SOFT;
+   case DNS_HARD: return dns_ip(ia,sa);
+  }
+
+ mx = (struct mx *) alloc(numanswers * sizeof(struct mx));
+ if (!mx) return DNS_MEM;
+ nummx = 0;
+
+ while ((r = findmx(T_MX)) != 2)
+  {
+   if (r == DNS_SOFT) { alloc_free(mx); return DNS_SOFT; }
+   if (r == 1)
+    {
+     mx[nummx].p = pref;
+     mx[nummx].sa.s = 0;
+     if (!stralloc_copys(&mx[nummx].sa,name))
+      {
+       while (nummx > 0) alloc_free(mx[--nummx].sa.s);
+       alloc_free(mx); return DNS_MEM;
+      }
+     ++nummx;
+    }
+  }
+
+ if (!nummx) return dns_ip(ia,sa); /* e.g., CNAME -> A */
+
+ flagsoft = 0;
+ while (nummx > 0)
+  {
+   unsigned long numsame;
+
+   i = 0;
+   numsame = 1;
+   for (j = 1;j < nummx;++j)
+     if (mx[j].p < mx[i].p)
+      {
+       i = j;
+       numsame = 1;
+      }
+     else if (mx[j].p == mx[i].p)
+      {
+       ++numsame;
+       random = random * 69069 + 1;
+       if ((random / 2) < (2147483647 / numsame))
+         i = j;
+      }
+
+   switch(dns_ipplus(ia,&mx[i].sa,mx[i].p))
+    {
+     case DNS_MEM: case DNS_SOFT:
+       flagsoft = 1; break;
+    }
+
+   alloc_free(mx[i].sa.s);
+   mx[i] = mx[--nummx];
+  }
+
+ alloc_free(mx);
+ return flagsoft;
+}
diff --git a/dns.h b/dns.h
new file mode 100644 (file)
index 0000000..bca9490
--- /dev/null
+++ b/dns.h
@@ -0,0 +1,14 @@
+#ifndef DNS_H
+#define DNS_H
+
+#define DNS_SOFT -1
+#define DNS_HARD -2
+#define DNS_MEM -3
+
+void dns_init();
+int dns_cname();
+int dns_mxip();
+int dns_ip();
+int dns_ptr();
+
+#endif
diff --git a/dnscname.c b/dnscname.c
new file mode 100644 (file)
index 0000000..b3cd6f1
--- /dev/null
@@ -0,0 +1,25 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "stralloc.h"
+#include "dns.h"
+#include "dnsdoe.h"
+#include "readwrite.h"
+#include "exit.h"
+
+stralloc sa = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ if (!argv[1]) _exit(100);
+
+ if (!stralloc_copys(&sa,argv[1]))
+  { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); }
+
+ dns_init(0);
+ dnsdoe(dns_cname(&sa));
+ substdio_putflush(subfdout,sa.s,sa.len);
+ substdio_putsflush(subfdout,"\n");
+ _exit(0);
+}
diff --git a/dnsdoe.c b/dnsdoe.c
new file mode 100644 (file)
index 0000000..806ca53
--- /dev/null
+++ b/dnsdoe.c
@@ -0,0 +1,16 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "exit.h"
+#include "dns.h"
+#include "dnsdoe.h"
+
+void dnsdoe(r)
+int r;
+{
+ switch (r)
+  {
+   case DNS_HARD: substdio_putsflush(subfderr,"hard error\n"); _exit(100);
+   case DNS_SOFT: substdio_putsflush(subfderr,"soft error\n"); _exit(111);
+   case DNS_MEM: substdio_putsflush(subfderr,"out of memory\n"); _exit(111);
+  }
+}
diff --git a/dnsdoe.h b/dnsdoe.h
new file mode 100644 (file)
index 0000000..0e47bf6
--- /dev/null
+++ b/dnsdoe.h
@@ -0,0 +1,6 @@
+#ifndef DNSDOE_H
+#define DNSDOE_H
+
+extern void dnsdoe();
+
+#endif
diff --git a/dnsfq.c b/dnsfq.c
new file mode 100644 (file)
index 0000000..b7619b9
--- /dev/null
+++ b/dnsfq.c
@@ -0,0 +1,32 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "stralloc.h"
+#include "dns.h"
+#include "dnsdoe.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "exit.h"
+
+stralloc sa = {0};
+ipalloc ia = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ if (!argv[1]) _exit(100);
+
+ if (!stralloc_copys(&sa,argv[1]))
+  { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); }
+
+ dns_init(1);
+ dnsdoe(dns_ip(&ia,&sa));
+ if (ia.len <= 0)
+  {
+   substdio_putsflush(subfderr,"no IP addresses\n"); _exit(100);
+  }
+ dnsdoe(dns_ptr(&sa,&ia.ix[0].ip));
+ substdio_putflush(subfdout,sa.s,sa.len);
+ substdio_putsflush(subfdout,"\n");
+ _exit(0);
+}
diff --git a/dnsip.c b/dnsip.c
new file mode 100644 (file)
index 0000000..e7b671c
--- /dev/null
+++ b/dnsip.c
@@ -0,0 +1,34 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "stralloc.h"
+#include "dns.h"
+#include "dnsdoe.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "exit.h"
+
+char temp[IPFMT];
+
+stralloc sa = {0};
+ipalloc ia = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int j;
+
+ if (!argv[1]) _exit(100);
+
+ if (!stralloc_copys(&sa,argv[1]))
+  { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); }
+
+ dns_init(0);
+ dnsdoe(dns_ip(&ia,&sa));
+ for (j = 0;j < ia.len;++j)
+  {
+   substdio_put(subfdout,temp,ip_fmt(temp,&ia.ix[j].ip));
+   substdio_putsflush(subfdout,"\n");
+  }
+ _exit(0);
+}
diff --git a/dnsmxip.c b/dnsmxip.c
new file mode 100644 (file)
index 0000000..6d8e137
--- /dev/null
+++ b/dnsmxip.c
@@ -0,0 +1,40 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "dns.h"
+#include "dnsdoe.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "now.h"
+#include "exit.h"
+
+char temp[IPFMT + FMT_ULONG];
+
+stralloc sa = {0};
+ipalloc ia = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int j;
+ unsigned long r;
+
+ if (!argv[1]) _exit(100);
+
+ if (!stralloc_copys(&sa,argv[1]))
+  { substdio_putsflush(subfderr,"out of memory\n"); _exit(111); }
+
+ r = now() + getpid();
+ dns_init(0);
+ dnsdoe(dns_mxip(&ia,&sa,r));
+ for (j = 0;j < ia.len;++j)
+  {
+   substdio_put(subfdout,temp,ip_fmt(temp,&ia.ix[j].ip));
+   substdio_puts(subfdout," ");
+   substdio_put(subfdout,temp,fmt_ulong(temp,(unsigned long) ia.ix[j].pref));
+   substdio_putsflush(subfdout,"\n");
+  }
+ _exit(0);
+}
diff --git a/dnsptr.c b/dnsptr.c
new file mode 100644 (file)
index 0000000..6a92fe0
--- /dev/null
+++ b/dnsptr.c
@@ -0,0 +1,27 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "stralloc.h"
+#include "str.h"
+#include "scan.h"
+#include "dns.h"
+#include "dnsdoe.h"
+#include "ip.h"
+#include "exit.h"
+
+stralloc sa = {0};
+struct ip_address ip;
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ if (!argv[1]) _exit(100);
+
+ ip_scan(argv[1],&ip);
+
+ dns_init(0);
+ dnsdoe(dns_ptr(&sa,&ip));
+ substdio_putflush(subfdout,sa.s,sa.len);
+ substdio_putsflush(subfdout,"\n");
+ _exit(0);
+}
diff --git a/dot-qmail.9 b/dot-qmail.9
new file mode 100644 (file)
index 0000000..936067c
--- /dev/null
@@ -0,0 +1,394 @@
+.TH dot-qmail 5
+.SH NAME
+dot-qmail \- control the delivery of mail messages
+.SH DESCRIPTION
+Normally the
+.B qmail-local
+program delivers each incoming message to your system mailbox,
+.IR homedir\fB/Mailbox ,
+where
+.I homedir
+is your home directory.
+
+It can instead
+write the mail to a different file or directory,
+forward it to another address,
+distribute it to a mailing list,
+or even execute programs,
+all under your control.
+.SH "THE QMAIL FILE"
+To change
+.BR qmail-local 's
+behavior, set up a
+.B .qmail
+file in your home directory.
+
+.B .qmail
+contains one or more lines.
+Each line is a delivery instruction.
+.B qmail-local
+follows each instruction in turn.
+There are five types of delivery instructions:
+(1) comment; (2) program; (3) forward; (4) mbox; (5) maildir.
+.TP 5
+(1)
+A comment line begins with a number sign:
+
+.EX
+     # this is a comment
+.EE
+
+.B qmail-local
+ignores the line.
+.TP 5
+(2)
+A program line begins with a vertical bar:
+
+.EX
+     |/usr/ucb/vacation djb
+.EE
+
+.B qmail-local
+takes the rest of the line as a command to supply to
+.BR sh .
+See
+.B qmail-command(8)
+for further information.
+.TP 5
+(3)
+A forward line begins with an ampersand:
+
+.EX
+     &me@new.job.com
+.EE
+
+.B qmail-local
+takes the rest of the line as a mail address;
+it uses
+.B qmail-queue
+to forward the message to that address.
+The address must contain a fully qualified domain name;
+it must not contain extra spaces, angle brackets, or comments:
+
+.EX
+     # the following examples are WRONG
+.br
+     &me@new
+.br
+     &<me@new.job.com>
+.br
+     & me@new.job.com
+.br
+     &me@new.job.com (New Address)
+.EE
+
+If the address begins with a letter or number,
+you may leave out the ampersand:
+
+.EX
+     me@new.job.com
+.EE
+
+Note that
+.B qmail-local
+omits its new
+.B Return-Path
+line when forwarding messages.
+.TP 5
+(4)
+An 
+.I mbox
+line begins with a slash or dot,
+and does not end with a slash:
+
+.EX
+     /home/djb/Mailbox.sos
+.EE
+
+.B qmail-local
+takes the entire line as a filename.
+It appends the mail message to that file,
+using
+.BR flock -style
+file locking if possible.
+.B qmail-local
+stores the mail message in
+.I mbox
+format, as described in
+.BR mbox(5) .
+
+.B WARNING:
+On many systems,
+anyone who can read a file can
+.B flock
+it, and thus hold up
+.BR qmail-local 's
+delivery forever.
+Do not deliver mail to a publicly accessible file!
+
+If
+.B qmail-local
+is able to lock the file, but has trouble writing to it
+(because, for example, the disk is full),
+it will truncate the file back to its original length.
+However, it cannot prevent mailbox corruption if the system
+crashes during delivery.
+.TP 5
+(5)
+A
+.I maildir
+line begins with a slash or dot,
+and ends with a slash:
+
+.EX
+     /home/djb/Maildir/
+.EE
+
+.B qmail-local
+takes the entire line as the name of a directory in
+.I maildir
+format.
+It reliably stores the incoming message in that directory.
+See
+.B maildir(5)
+for more details.
+.PP
+If
+.B .qmail
+has the execute bit set,
+it must not contain any
+program lines,
+.I mbox
+lines,
+or
+.I maildir
+lines.
+If
+.B qmail-local
+sees any such lines,
+it will stop and indicate a temporary failure.
+
+If
+.B .qmail
+is completely empty (0 bytes long), or does not exist,
+.B qmail-local
+follows the
+.I aliasempty
+instructions set by your system administrator;
+normally
+.I aliasempty
+is
+.BR ./Mailbox ,
+so
+.B qmail-local
+appends the mail message to
+.B Mailbox
+in
+.I mbox
+format.
+
+.B .qmail
+may contain extra spaces and tabs at the end of a line.
+Blank lines are allowed, but not for the first line of
+.BR .qmail .
+
+If
+.B .qmail
+is world-writable or group-writable,
+.B qmail-local
+stops and indicates a temporary failure.
+.SH "SAFE QMAIL EDITING"
+Incoming messages can arrive at any moment.
+If you want to safely edit your
+.B .qmail
+file, first set the sticky bit on your home directory:
+
+.EX
+     chmod +t $HOME
+.EE
+
+.B qmail-local
+will temporarily defer delivery of any message to you
+if your home directory is sticky
+(or group-writable or other-writable,
+which should never happen).
+Make sure to
+
+.EX
+     chmod -t $HOME
+.EE
+
+when you are done!
+It's a good idea to test your new
+.B .qmail
+file as follows:
+
+.EX
+     qmail-local -n $USER $HOME $USER '' '' '' ''
+.EE
+.SH "EXTENSION ADDRESSES"
+In the
+.B qmail
+system,
+you control all local addresses of the form
+.IR user\fBBREAK\fIanything ,
+as well as the address
+.I user
+itself,
+where
+.I user
+is your account name.
+Delivery to
+.I user\fBBREAK\fIanything
+is controlled by the file
+.IR homedir/\fB.qmail\-\fIanything .
+(These rules may be changed by the system administrator;
+see
+.BR qmail-users (5).)
+
+The
+.B alias
+user controls all other addresses.
+Delivery to
+.I local
+is controlled by the file
+.IR homedir/\fB.qmail\-\fIlocal ,
+where
+.I homedir
+is
+.BR alias 's
+home directory.
+
+In the following description,
+.B qmail-local
+is handling a message addressed to
+.IR local@domain ,
+where
+.I local
+is controlled by
+.BR .qmail\-\fIext .
+Here is what it does.
+
+If
+.B .qmail\-\fIext
+is completely empty,
+.B qmail-local
+follows the
+.I aliasempty
+instructions set by your system administrator.
+
+If
+.B .qmail\-\fIext
+doesn't exist,
+.B qmail-local
+will try some default
+.B .qmail
+files.
+For example,
+if
+.I ext
+is
+.BR foo-bar ,
+.B qmail-local
+will try first
+.BR .qmail-foo-bar ,
+then
+.BR .qmail-foo-default ,
+and finally
+.BR .qmail-default .
+If none of these exist,
+.B qmail-local
+will bounce the message.
+(Exception: for the basic
+.I user
+address,
+.B qmail-local
+treats a nonexistent
+.B .qmail
+the same as an empty
+.BR .qmail .)
+
+.B WARNING:
+For security,
+.B qmail-local
+replaces any dots in
+.I ext
+with colons before checking
+.BR .qmail\-\fIext .
+For convenience,
+.B qmail-local
+converts any uppercase letters in
+.I ext
+to lowercase.
+
+When
+.B qmail-local
+forwards a message as instructed in
+.B .qmail\-\fIext
+(or
+.BR .qmail-default ),
+it checks whether
+.B .qmail\-\fIext\fB-owner\fP
+exists.
+If so,
+it uses
+.I local\fB-owner@\fIdomain
+as the envelope sender for the forwarded message.
+Otherwise it retains the envelope sender of the original message.
+Exception:
+.B qmail-local
+always retains the original envelope sender
+if it is the empty address or
+.BR #@[] ,
+i.e., if this is a bounce message.
+
+.B qmail-local
+also supports
+.B variable envelope return paths
+(VERPs):
+if
+.B .qmail\-\fIext\fB-owner\fP
+and
+.B .qmail\-\fIext\fB-owner-default\fP
+both exist, it uses
+.I local\fB\-owner\-@\fIdomain\fB-@[]
+as the envelope sender.
+This will cause a recipient
+.I recip\fB@\fIreciphost
+to see an envelope sender of
+.IR local\fB\-owner\-\fIrecip\fB=\fIreciphost\fB@\fIdomain .
+.SH "ERROR HANDLING"
+If a delivery instruction fails,
+.B qmail-local
+stops immediately and reports failure.
+.B qmail-local
+handles forwarding after all other instructions,
+so any error in another type of delivery will prevent all forwarding.
+
+If a program returns exit code 99,
+.B qmail-local
+ignores all succeeding lines in
+.BR .qmail ,
+but it still pays attention to previous forward lines.
+
+To set up independent instructions,
+where a temporary or permanent failure in one instruction
+does not affect the others,
+move each instruction into a separate
+.B .qmail\-\fIext
+file, and set up a central
+.B .qmail
+file that forwards to all of the
+.BR .qmail\-\fIext s.
+Note that
+.B qmail-local
+can handle any number of forward lines simultaneously.
+.SH "SEE ALSO"
+envelopes(5),
+maildir(5),
+mbox(5),
+qmail-users(5),
+qmail-local(8),
+qmail-command(8),
+qmail-queue(8),
+qmail-lspawn(8)
diff --git a/elq.sh b/elq.sh
new file mode 100644 (file)
index 0000000..7e20262
--- /dev/null
+++ b/elq.sh
@@ -0,0 +1 @@
+QMAIL/bin/maildir2mbox && exec elm ${1+"$@"}
diff --git a/env.3 b/env.3
new file mode 100644 (file)
index 0000000..53a5f89
--- /dev/null
+++ b/env.3
@@ -0,0 +1,31 @@
+.TH env 3
+.SH NAME
+env \- manage the environment
+.SH SYNTAX
+.B #include <env.h>
+
+char **\fBenviron\fP;
+
+char *\fBenv_get\fP(\fIname\fR);
+.br
+char *\fBenv_pick\fP();
+
+char *\fIname\fR;
+.SH DESCRIPTION
+The environment,
+.BR environ ,
+is a 0-terminated array of 0-terminated strings,
+called environment variables.
+Each environment variable is of the form
+.IR name\fB=\fIvalue .
+
+.B env_get
+returns the value of the first variable whose name is
+.IR name ,
+or 0 if there is no such variable.
+
+.B env_pick
+returns any variable in the environment,
+or 0 if the environment is empty.
+.SH "SEE ALSO"
+environ(7)
diff --git a/env.c b/env.c
new file mode 100644 (file)
index 0000000..05d527b
--- /dev/null
+++ b/env.c
@@ -0,0 +1,113 @@
+/* env.c, envread.c, env.h: environ library
+Daniel J. Bernstein, djb@silverton.berkeley.edu.
+Depends on str.h, alloc.h.
+Requires environ.
+19960113: rewrite. warning: interface is different.
+No known patent problems.
+*/
+
+#include "str.h"
+#include "alloc.h"
+#include "env.h"
+
+int env_isinit = 0; /* if env_isinit: */
+static int ea; /* environ is a pointer to ea+1 char*'s. */
+static int en; /* the first en of those are ALLOCATED. environ[en] is 0. */
+
+static void env_goodbye(i) int i;
+{
+ alloc_free(environ[i]);
+ environ[i] = environ[--en];
+ environ[en] = 0;
+}
+
+static char *null = 0;
+
+void env_clear()
+{
+ if (env_isinit) while (en) env_goodbye(0);
+ else environ = &null;
+}
+
+static void env_unsetlen(s,len) char *s; int len;
+{
+ int i;
+ for (i = en - 1;i >= 0;--i)
+   if (!str_diffn(s,environ[i],len))
+     if (environ[i][len] == '=')
+       env_goodbye(i);
+}
+
+int env_unset(s) char *s;
+{
+ if (!env_isinit) if (!env_init()) return 0;
+ env_unsetlen(s,str_len(s));
+ return 1;
+}
+
+static int env_add(s) char *s;
+{
+ char *t;
+ t = env_findeq(s);
+ if (t) env_unsetlen(s,t - s);
+ if (en == ea)
+  {
+   ea += 30;
+   if (!alloc_re(&environ,(en + 1) * sizeof(char *),(ea + 1) * sizeof(char *)))
+    { ea = en; return 0; }
+  }
+ environ[en++] = s;
+ environ[en] = 0;
+ return 1;
+}
+
+int env_put(s) char *s;
+{
+ char *u;
+ if (!env_isinit) if (!env_init()) return 0;
+ u = alloc(str_len(s) + 1);
+ if (!u) return 0;
+ str_copy(u,s);
+ if (!env_add(u)) { alloc_free(u); return 0; }
+ return 1;
+}
+
+int env_put2(s,t) char *s; char *t;
+{
+ char *u;
+ int slen;
+ if (!env_isinit) if (!env_init()) return 0;
+ slen = str_len(s);
+ u = alloc(slen + str_len(t) + 2);
+ if (!u) return 0;
+ str_copy(u,s);
+ u[slen] = '=';
+ str_copy(u + slen + 1,t);
+ if (!env_add(u)) { alloc_free(u); return 0; }
+ return 1;
+}
+
+int env_init()
+{
+ char **newenviron;
+ int i;
+ for (en = 0;environ[en];++en) ;
+ ea = en + 10;
+ newenviron = (char **) alloc((ea + 1) * sizeof(char *));
+ if (!newenviron) return 0;
+ for (en = 0;environ[en];++en)
+  {
+   newenviron[en] = alloc(str_len(environ[en]) + 1);
+   if (!newenviron[en])
+    {
+     for (i = 0;i < en;++i) alloc_free(newenviron[i]);
+     alloc_free(newenviron);
+     return 0;
+    }
+   str_copy(newenviron[en],environ[en]);
+  }
+ newenviron[en] = 0;
+ environ = newenviron;
+ env_isinit = 1;
+ return 1;
+}
diff --git a/env.h b/env.h
new file mode 100644 (file)
index 0000000..9befc79
--- /dev/null
+++ b/env.h
@@ -0,0 +1,17 @@
+#ifndef ENV_H
+#define ENV_H
+
+extern int env_isinit;
+
+extern int env_init();
+extern int env_put();
+extern int env_put2();
+extern int env_unset();
+extern /*@null@*/char *env_get();
+extern char *env_pick();
+extern void env_clear();
+extern char *env_findeq();
+
+extern char **environ;
+
+#endif
diff --git a/envelopes.5 b/envelopes.5
new file mode 100644 (file)
index 0000000..5f7084a
--- /dev/null
@@ -0,0 +1,231 @@
+.TH envelopes 5
+.SH "NAME"
+envelopes \- sender/recipient lists attached to messages
+.SH "INTRODUCTION"
+Electronic mail messages are delivered in
+.IR envelopes .
+
+An envelope lists a
+.I sender
+and one or more
+.IR recipients .
+Usually these
+envelope addresses are the same
+as the addresses listed in the message header:
+
+.EX
+   (envelope) from djb to root
+.br
+   From: djb
+.br
+   To: root
+.EE
+
+In more complicated situations, though,
+the envelope addresses may differ from the header addresses.
+.SH "ENVELOPE EXAMPLES"
+When a message is delivered to
+several people at different locations,
+it is first photocopied
+and placed into several envelopes:
+
+.EX
+   (envelope) from djb to root
+.br
+   From: djb                          Copy #1 of message
+.br
+   To: root, god@brl.mil
+.EE
+
+.EX
+   (envelope) from djb to god@brl.mil
+.br
+   From: djb                          Copy #2 of message
+.br
+   To: root, god@brl.mil
+.EE
+
+When a message is delivered
+to several people at the same location,
+the sender doesn't have to photocopy it.
+He can instead stuff it into
+one envelope with several addresses;
+the recipients will make the photocopy:
+
+.EX
+   (envelope) from djb to god@brl.mil, angel@brl.mil
+.br
+   From: djb
+.br
+   To: god@brl.mil, angel@brl.mil, joe, frde
+.EE
+
+Bounced mail is sent back to the envelope sender address.
+The bounced mail doesn't list an envelope sender,
+so bounce loops are impossible:
+
+.EX
+   (envelope) from <> to djb
+.br
+   From: MAILER-DAEMON
+.br
+   To: djb
+.br
+   Subject: unknown user frde
+.EE
+
+The recipient of a message may make another copy
+and forward it in a new envelope:
+
+.EX
+   (envelope) from djb to joe
+.br
+   From: djb                          Original message
+.br
+   To: joe
+.EE
+
+.EX
+   (envelope) from joe to fred
+.br
+   From: djb                          Forwarded message
+.br
+   To: joe
+.EE
+
+A mailing list works almost the same way:
+
+.EX
+   (envelope) from djb to sos-list
+.br
+   From: djb                          Original message
+.br
+   To: sos-list
+.EE
+
+.EX
+   (envelope) from sos-owner to god@brl.mil
+.br
+   From: djb                          Forwarded message
+.br
+   To: sos-list                       to recipient #1
+.EE
+
+.EX
+   (envelope) from sos-owner to frde
+.br
+   From: djb                          Forwarded message
+.br
+   To: sos-list                       to recipient #2
+.EE
+
+Notice that the mailing list is set up
+to replace the envelope sender with something new,
+.BR sos-owner .
+So bounces will come back to
+.BR sos-owner :
+
+.EX
+   (envelope) from <> to sos-owner
+.br
+   From: MAILER-DAEMON
+.br
+   To: sos-owner
+.br
+   Subject: unknown user frde
+.EE
+
+It's a good idea to set up an extra address,
+.BR sos-owner ,
+like this:
+the original envelope sender (\fBdjb\fP)
+has no way to fix bad
+.B sos-list
+addresses,
+and of course bounces must not be sent to 
+.B sos-list
+itself.
+.SH "HOW ENVELOPE ADDRESSES ARE STORED"
+Envelope sender and envelope recipient addresses
+are transmitted and recorded in several ways.
+
+When a user injects mail through
+.BR qmail-inject ,
+he can supply a
+.B Return-Path
+line or a
+.B \-f
+option for the envelope sender;
+by default the envelope sender is his login name.
+The envelope recipient addresses can be taken
+from the command line or from various header fields,
+depending on the options to
+.BR qmail-inject .
+Similar comments apply to
+.BR sendmail .
+
+When a message is transferred from one machine to another through SMTP,
+the envelope sender is given in a
+.B MAIL FROM
+command,
+the envelope recipients are given in 
+.B RCPT TO
+commands,
+and the message is supplied separately by a 
+.B DATA
+command.
+
+When a message is delivered by
+.B qmail
+to a single local recipient,
+.B qmail-local
+records the recipient in
+.B Delivered-To
+and the envelope sender in
+.BR Return-Path .
+It uses
+.B Delivered-To
+to detect mail forwarding loops.
+
+.B sendmail
+normally records the envelope sender in
+.BR Return-Path .
+It does not record envelope recipient addresses,
+on the theory that they are redundant:
+you received the mail,
+so you must have been one of the envelope recipients.
+
+Note that,
+if the header doesn't have any recipient addresses,
+.B sendmail
+will move envelope recipient addresses back into the header.
+This situation occurs if all addresses were originally listed as
+.BR Bcc ,
+since
+.B Bcc
+is automatically removed.
+When
+.B sendmail
+sees this, it creates a new
+.B Apparently-To
+header field with the envelope recipient addresses.
+This has the strange effect that each blind-carbon-copy recipient will see
+a list of all recipients on the same machine.
+
+When a message is stored in
+.B mbox
+format,
+the envelope sender is recorded at the top of the message
+as a UUCP-style
+.B From
+(no colon) line.
+Note that this line is less reliable than the
+.B Return-Path
+line added by
+.B qmail-local
+or
+.B sendmail\fP.
+.SH "SEE ALSO"
+qmail-header(5),
+qmail-local(8),
+qmail-inject(8)
diff --git a/envread.c b/envread.c
new file mode 100644 (file)
index 0000000..80185de
--- /dev/null
+++ b/envread.c
@@ -0,0 +1,30 @@
+#include "env.h"
+#include "str.h"
+
+extern /*@null@*/char *env_get(s)
+char *s;
+{
+  int i;
+  unsigned int slen;
+  char *envi;
+  slen = str_len(s);
+  for (i = 0;envi = environ[i];++i)
+    if ((!str_diffn(s,envi,slen)) && (envi[slen] == '='))
+      return envi + slen + 1;
+  return 0;
+}
+
+extern char *env_pick()
+{
+  return environ[0];
+}
+
+extern char *env_findeq(s)
+char *s;
+{
+  for (;*s;++s)
+    if (*s == '=')
+      return s;
+  return 0;
+}
diff --git a/error.3 b/error.3
new file mode 100644 (file)
index 0000000..e955b35
--- /dev/null
+++ b/error.3
@@ -0,0 +1,45 @@
+.TH error 3
+.SH NAME
+error \- syscall error codes
+.SH SYNTAX
+.B #include <error.h>
+
+extern int \fBerrno\fP;
+
+extern int \fBerror_intr\fP;
+.br
+extern int \fBerror_nomem\fP;
+.br
+extern int \fBerror_noent\fP;
+.br
+extern int \fBerror_txtbsy\fP;
+.br
+extern int \fBerror_io\fP;
+.br
+extern int \fBerror_exist\fP;
+.br
+extern int \fBerror_timeout\fP;
+.br
+extern int \fBerror_inprogress\fP;
+.br
+extern int \fBerror_wouldblock\fP;
+.br
+extern int \fBerror_again\fP;
+.br
+extern int \fBerror_pipe\fP;
+.br
+extern int \fBerror_perm\fP;
+.br
+extern int \fBerror_acces\fP;
+.SH DESCRIPTION
+UNIX syscalls provide detailed error codes in the
+.B errno
+variable.
+The
+.B error
+library provides portable names for a variety of possible
+.B errno
+values.
+.SH "SEE ALSO"
+error_str(3),
+error_temp(3)
diff --git a/error.c b/error.c
new file mode 100644 (file)
index 0000000..d51304f
--- /dev/null
+++ b/error.c
@@ -0,0 +1,95 @@
+#include <errno.h>
+#include "error.h"
+
+/* warning: as coverage improves here, should update error_{str,temp} */
+
+int error_intr =
+#ifdef EINTR
+EINTR;
+#else
+-1;
+#endif
+
+int error_nomem =
+#ifdef ENOMEM
+ENOMEM;
+#else
+-2;
+#endif
+
+int error_noent = 
+#ifdef ENOENT
+ENOENT;
+#else
+-3;
+#endif
+
+int error_txtbsy =
+#ifdef ETXTBSY
+ETXTBSY;
+#else
+-4;
+#endif
+
+int error_io =
+#ifdef EIO
+EIO;
+#else
+-5;
+#endif
+
+int error_exist =
+#ifdef EEXIST
+EEXIST;
+#else
+-6;
+#endif
+
+int error_timeout =
+#ifdef ETIMEDOUT
+ETIMEDOUT;
+#else
+-7;
+#endif
+
+int error_inprogress =
+#ifdef EINPROGRESS
+EINPROGRESS;
+#else
+-8;
+#endif
+
+int error_wouldblock =
+#ifdef EWOULDBLOCK
+EWOULDBLOCK;
+#else
+-9;
+#endif
+
+int error_again =
+#ifdef EAGAIN
+EAGAIN;
+#else
+-10;
+#endif
+
+int error_pipe =
+#ifdef EPIPE
+EPIPE;
+#else
+-11;
+#endif
+
+int error_perm =
+#ifdef EPERM
+EPERM;
+#else
+-12;
+#endif
+
+int error_acces =
+#ifdef EACCES
+EACCES;
+#else
+-13;
+#endif
diff --git a/error.h b/error.h
new file mode 100644 (file)
index 0000000..01bd3dc
--- /dev/null
+++ b/error.h
@@ -0,0 +1,23 @@
+#ifndef ERROR_H
+#define ERROR_H
+
+extern int errno;
+
+extern int error_intr;
+extern int error_nomem;
+extern int error_noent;
+extern int error_txtbsy;
+extern int error_io;
+extern int error_exist;
+extern int error_timeout;
+extern int error_inprogress;
+extern int error_wouldblock;
+extern int error_again;
+extern int error_pipe;
+extern int error_perm;
+extern int error_acces;
+
+extern char *error_str();
+extern int error_temp();
+
+#endif
diff --git a/error_str.3 b/error_str.3
new file mode 100644 (file)
index 0000000..62043c4
--- /dev/null
@@ -0,0 +1,19 @@
+.TH error_str 3
+.SH NAME
+error_str \- names for syscall error codes
+.SH SYNTAX
+.B #include <error.h>
+
+char *\fBerror_str\fP(\fIe\fR);
+
+int \fIe\fR;
+.SH DESCRIPTION
+.B error_str
+returns a printable string describing syscall error code
+.IR e .
+Normally
+.I e
+is
+.BR errno .
+.SH "SEE ALSO"
+error(3)
diff --git a/error_str.c b/error_str.c
new file mode 100644 (file)
index 0000000..804d1fa
--- /dev/null
@@ -0,0 +1,276 @@
+#include <errno.h>
+#include "error.h"
+
+#define X(e,s) if (i == e) return s;
+
+char *error_str(i)
+int i;
+{
+  X(0,"no error")
+  X(error_intr,"interrupted system call")
+  X(error_nomem,"out of memory")
+  X(error_noent,"file does not exist")
+  X(error_txtbsy,"text busy")
+  X(error_io,"input/output error")
+  X(error_exist,"file already exists")
+  X(error_timeout,"timed out")
+  X(error_inprogress,"operation in progress")
+  X(error_again,"temporary failure")
+  X(error_wouldblock,"input/output would block")
+  X(error_pipe,"broken pipe")
+  X(error_perm,"permission denied")
+  X(error_acces,"access denied")
+#ifdef ESRCH
+  X(ESRCH,"no such process")
+#endif
+#ifdef ENXIO
+  X(ENXIO,"device not configured")
+#endif
+#ifdef E2BIG
+  X(E2BIG,"argument list too long")
+#endif
+#ifdef ENOEXEC
+  X(ENOEXEC,"exec format error")
+#endif
+#ifdef EBADF
+  X(EBADF,"file descriptor not open")
+#endif
+#ifdef ECHILD
+  X(ECHILD,"no child processes")
+#endif
+#ifdef EDEADLK
+  X(EDEADLK,"operation would cause deadlock")
+#endif
+#ifdef EFAULT
+  X(EFAULT,"bad address")
+#endif
+#ifdef ENOTBLK
+  X(ENOTBLK,"not a block device")
+#endif
+#ifdef EBUSY
+  X(EBUSY,"device busy")
+#endif
+#ifdef EXDEV
+  X(EXDEV,"cross-device link")
+#endif
+#ifdef ENODEV
+  X(ENODEV,"device does not support operation")
+#endif
+#ifdef ENOTDIR
+  X(ENOTDIR,"not a directory")
+#endif
+#ifdef EISDIR
+  X(EISDIR,"is a directory")
+#endif
+#ifdef EINVAL
+  X(EINVAL,"invalid argument")
+#endif
+#ifdef ENFILE
+  X(ENFILE,"system cannot open more files")
+#endif
+#ifdef EMFILE
+  X(EMFILE,"process cannot open more files")
+#endif
+#ifdef ENOTTY
+  X(ENOTTY,"not a tty")
+#endif
+#ifdef EFBIG
+  X(EFBIG,"file too big")
+#endif
+#ifdef ENOSPC
+  X(ENOSPC,"out of disk space")
+#endif
+#ifdef ESPIPE
+  X(ESPIPE,"unseekable descriptor")
+#endif
+#ifdef EROFS
+  X(EROFS,"read-only file system")
+#endif
+#ifdef EMLINK
+  X(EMLINK,"too many links")
+#endif
+#ifdef EDOM
+  X(EDOM,"input out of range")
+#endif
+#ifdef ERANGE
+  X(ERANGE,"output out of range")
+#endif
+#ifdef EALREADY
+  X(EALREADY,"operation already in progress")
+#endif
+#ifdef ENOTSOCK
+  X(ENOTSOCK,"not a socket")
+#endif
+#ifdef EDESTADDRREQ
+  X(EDESTADDRREQ,"destination address required")
+#endif
+#ifdef EMSGSIZE
+  X(EMSGSIZE,"message too long")
+#endif
+#ifdef EPROTOTYPE
+  X(EPROTOTYPE,"incorrect protocol type")
+#endif
+#ifdef ENOPROTOOPT
+  X(ENOPROTOOPT,"protocol not available")
+#endif
+#ifdef EPROTONOSUPPORT
+  X(EPROTONOSUPPORT,"protocol not supported")
+#endif
+#ifdef ESOCKTNOSUPPORT
+  X(ESOCKTNOSUPPORT,"socket type not supported")
+#endif
+#ifdef EOPNOTSUPP
+  X(EOPNOTSUPP,"operation not supported")
+#endif
+#ifdef EPFNOSUPPORT
+  X(EPFNOSUPPORT,"protocol family not supported")
+#endif
+#ifdef EAFNOSUPPORT
+  X(EAFNOSUPPORT,"address family not supported")
+#endif
+#ifdef EADDRINUSE
+  X(EADDRINUSE,"address already used")
+#endif
+#ifdef EADDRNOTAVAIL
+  X(EADDRNOTAVAIL,"address not available")
+#endif
+#ifdef ENETDOWN
+  X(ENETDOWN,"network down")
+#endif
+#ifdef ENETUNREACH
+  X(ENETUNREACH,"network unreachable")
+#endif
+#ifdef ENETRESET
+  X(ENETRESET,"network reset")
+#endif
+#ifdef ECONNABORTED
+  X(ECONNABORTED,"connection aborted")
+#endif
+#ifdef ECONNRESET
+  X(ECONNRESET,"connection reset")
+#endif
+#ifdef ENOBUFS
+  X(ENOBUFS,"out of buffer space")
+#endif
+#ifdef EISCONN
+  X(EISCONN,"already connected")
+#endif
+#ifdef ENOTCONN
+  X(ENOTCONN,"not connected")
+#endif
+#ifdef ESHUTDOWN
+  X(ESHUTDOWN,"socket shut down")
+#endif
+#ifdef ETOOMANYREFS
+  X(ETOOMANYREFS,"too many references")
+#endif
+#ifdef ECONNREFUSED
+  X(ECONNREFUSED,"connection refused")
+#endif
+#ifdef ELOOP
+  X(ELOOP,"symbolic link loop")
+#endif
+#ifdef ENAMETOOLONG
+  X(ENAMETOOLONG,"file name too long")
+#endif
+#ifdef EHOSTDOWN
+  X(EHOSTDOWN,"host down")
+#endif
+#ifdef EHOSTUNREACH
+  X(EHOSTUNREACH,"host unreachable")
+#endif
+#ifdef ENOTEMPTY
+  X(ENOTEMPTY,"directory not empty")
+#endif
+#ifdef EPROCLIM
+  X(EPROCLIM,"too many processes")
+#endif
+#ifdef EUSERS
+  X(EUSERS,"too many users")
+#endif
+#ifdef EDQUOT
+  X(EDQUOT,"disk quota exceeded")
+#endif
+#ifdef ESTALE
+  X(ESTALE,"stale NFS file handle")
+#endif
+#ifdef EREMOTE
+  X(EREMOTE,"too many levels of remote in path")
+#endif
+#ifdef EBADRPC
+  X(EBADRPC,"RPC structure is bad")
+#endif
+#ifdef ERPCMISMATCH
+  X(ERPCMISMATCH,"RPC version mismatch")
+#endif
+#ifdef EPROGUNAVAIL
+  X(EPROGUNAVAIL,"RPC program unavailable")
+#endif
+#ifdef EPROGMISMATCH
+  X(EPROGMISMATCH,"program version mismatch")
+#endif
+#ifdef EPROCUNAVAIL
+  X(EPROCUNAVAIL,"bad procedure for program")
+#endif
+#ifdef ENOLCK
+  X(ENOLCK,"no locks available")
+#endif
+#ifdef ENOSYS
+  X(ENOSYS,"system call not available")
+#endif
+#ifdef EFTYPE
+  X(EFTYPE,"bad file type")
+#endif
+#ifdef EAUTH
+  X(EAUTH,"authentication error")
+#endif
+#ifdef ENEEDAUTH
+  X(ENEEDAUTH,"not authenticated")
+#endif
+#ifdef ENOSTR
+  X(ENOSTR,"not a stream device")
+#endif
+#ifdef ETIME
+  X(ETIME,"timer expired")
+#endif
+#ifdef ENOSR
+  X(ENOSR,"out of stream resources")
+#endif
+#ifdef ENOMSG
+  X(ENOMSG,"no message of desired type")
+#endif
+#ifdef EBADMSG
+  X(EBADMSG,"bad message type")
+#endif
+#ifdef EIDRM
+  X(EIDRM,"identifier removed")
+#endif
+#ifdef ENONET
+  X(ENONET,"machine not on network")
+#endif
+#ifdef ERREMOTE
+  X(ERREMOTE,"object not local")
+#endif
+#ifdef ENOLINK
+  X(ENOLINK,"link severed")
+#endif
+#ifdef EADV
+  X(EADV,"advertise error")
+#endif
+#ifdef ESRMNT
+  X(ESRMNT,"srmount error")
+#endif
+#ifdef ECOMM
+  X(ECOMM,"communication error")
+#endif
+#ifdef EPROTO
+  X(EPROTO,"protocol error")
+#endif
+#ifdef EMULTIHOP
+  X(EMULTIHOP,"multihop attempted")
+#endif
+#ifdef EREMCHG
+  X(EREMCHG,"remote address changed")
+#endif
+  return "unknown error";
+}
diff --git a/error_temp.3 b/error_temp.3
new file mode 100644 (file)
index 0000000..2f8229d
--- /dev/null
@@ -0,0 +1,27 @@
+.TH error_temp 3
+.SH NAME
+error_temp \- identify soft syscall error codes
+.SH SYNTAX
+.B #include <error.h>
+
+int \fBerror_temp\fP(\fIe\fR);
+
+int \fIe\fR;
+.SH DESCRIPTION
+.B error_temp
+returns 1 if syscall error code
+.I e
+is a soft error, 0 if it is a hard error.
+Normally
+.I e
+is
+.BR errno .
+
+A hard error is persistent:
+file not found, read-only file system, symbolic link loop, etc.
+
+A soft error is usually transient:
+out of memory, out of disk space, I/O error, disk quota exceeded,
+connection refused, host unreachable, etc.
+.SH "SEE ALSO"
+error(3)
diff --git a/error_temp.c b/error_temp.c
new file mode 100644 (file)
index 0000000..6782cef
--- /dev/null
@@ -0,0 +1,80 @@
+#include <errno.h>
+#include "error.h"
+
+#define X(n) if (e == n) return 1;
+
+int error_temp(e)
+int e;
+{
+  X(error_intr)
+  X(error_nomem)
+  X(error_txtbsy)
+  X(error_io)
+  X(error_timeout)
+  X(error_wouldblock)
+  X(error_again)
+#ifdef EDEADLK
+  X(EDEADLK)
+#endif
+#ifdef EBUSY
+  X(EBUSY)
+#endif
+#ifdef ENFILE
+  X(ENFILE)
+#endif
+#ifdef EMFILE
+  X(EMFILE)
+#endif
+#ifdef EFBIG
+  X(EFBIG)
+#endif
+#ifdef ENOSPC
+  X(ENOSPC)
+#endif
+#ifdef ENETDOWN
+  X(ENETDOWN)
+#endif
+#ifdef ENETUNREACH
+  X(ENETUNREACH)
+#endif
+#ifdef ENETRESET
+  X(ENETRESET)
+#endif
+#ifdef ECONNABORTED
+  X(ECONNABORTED)
+#endif
+#ifdef ECONNRESET
+  X(ECONNRESET)
+#endif
+#ifdef ENOBUFS
+  X(ENOBUFS)
+#endif
+#ifdef ETOOMANYREFS
+  X(ETOOMANYREFS)
+#endif
+#ifdef ECONNREFUSED
+  X(ECONNREFUSED)
+#endif
+#ifdef EHOSTDOWN
+  X(EHOSTDOWN)
+#endif
+#ifdef EHOSTUNREACH
+  X(EHOSTUNREACH)
+#endif
+#ifdef EPROCLIM
+  X(EPROCLIM)
+#endif
+#ifdef EUSERS
+  X(EUSERS)
+#endif
+#ifdef EDQUOT
+  X(EDQUOT)
+#endif
+#ifdef ESTALE
+  X(ESTALE)
+#endif
+#ifdef ENOLCK
+  X(ENOLCK)
+#endif
+  return 0;
+}
diff --git a/exit.h b/exit.h
new file mode 100644 (file)
index 0000000..39011c8
--- /dev/null
+++ b/exit.h
@@ -0,0 +1,6 @@
+#ifndef EXIT_H
+#define EXIT_H
+
+extern void _exit();
+
+#endif
diff --git a/extra.h b/extra.h
new file mode 100644 (file)
index 0000000..c598175
--- /dev/null
+++ b/extra.h
@@ -0,0 +1,7 @@
+#ifndef EXTRA_H
+#define EXTRA_H
+
+#define QUEUE_EXTRA ""
+#define QUEUE_EXTRALEN 0
+
+#endif
diff --git a/fd.h b/fd.h
new file mode 100644 (file)
index 0000000..c3d6e3e
--- /dev/null
+++ b/fd.h
@@ -0,0 +1,7 @@
+#ifndef FD_H
+#define FD_H
+
+extern int fd_copy();
+extern int fd_move();
+
+#endif
diff --git a/fd_copy.3 b/fd_copy.3
new file mode 100644 (file)
index 0000000..758a7e7
--- /dev/null
+++ b/fd_copy.3
@@ -0,0 +1,44 @@
+.TH fd_copy 3
+.SH NAME
+fd_copy \- duplicate a descriptor
+.SH SYNTAX
+.B #include <fd.h>
+
+int \fBfd_copy\fP(\fIto\fR,\fIfrom\fR);
+
+int \fIto\fR;
+.br
+int \fIfrom\fR;
+.SH DESCRIPTION
+.B fd_copy
+copies 
+descriptor
+.I from
+to descriptor
+.IR to .
+If
+.I to
+was already open,
+.B fd_copy
+closes it.
+.B fd_copy
+always leaves
+.I from
+intact;
+if
+.I to
+and
+.I from
+are the same number,
+.B fd_copy
+does nothing.
+
+.B fd_copy
+returns 0 on success, -1 on error.
+.B fd_copy
+does not guarantee that
+.I to
+will remain open, if it was open, in case of error.
+.SH "SEE ALSO"
+dup(2),
+fd_move(3)
diff --git a/fd_copy.c b/fd_copy.c
new file mode 100644 (file)
index 0000000..b9f7167
--- /dev/null
+++ b/fd_copy.c
@@ -0,0 +1,13 @@
+#include <fcntl.h>
+#include "fd.h"
+
+int fd_copy(to,from)
+int to;
+int from;
+{
+  if (to == from) return 0;
+  if (fcntl(from,F_GETFL,0) == -1) return -1;
+  close(to);
+  if (fcntl(from,F_DUPFD,to) == -1) return -1;
+  return 0;
+}
diff --git a/fd_move.3 b/fd_move.3
new file mode 100644 (file)
index 0000000..94aa1b7
--- /dev/null
+++ b/fd_move.3
@@ -0,0 +1,41 @@
+.TH fd_move 3
+.SH NAME
+fd_move \- renumber a descriptor
+.SH SYNTAX
+.B #include <fd.h>
+
+int \fBfd_move\fP(\fIto\fR,\fIfrom\fR);
+
+int \fIto\fR;
+.br
+int \fIfrom\fR;
+.SH DESCRIPTION
+.B fd_move
+moves
+descriptor
+.I from
+to descriptor
+.IR to .
+If
+.I to
+was already open,
+.B fd_move
+closes it.
+If the move is successful,
+.B fd_move
+closes
+.IR from .
+Exception:
+if
+.I to
+and
+.I from
+are the same number,
+.B fd_move
+does nothing.
+
+.B fd_move
+returns 0 on success, -1 on error.
+.SH "SEE ALSO"
+dup(2),
+fd_copy(3)
diff --git a/fd_move.c b/fd_move.c
new file mode 100644 (file)
index 0000000..1aa557f
--- /dev/null
+++ b/fd_move.c
@@ -0,0 +1,11 @@
+#include "fd.h"
+
+int fd_move(to,from)
+int to;
+int from;
+{
+  if (to == from) return 0;
+  if (fd_copy(to,from) == -1) return -1;
+  close(from);
+  return 0;
+}
diff --git a/fifo.c b/fifo.c
new file mode 100644 (file)
index 0000000..1f3d150
--- /dev/null
+++ b/fifo.c
@@ -0,0 +1,10 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "hasmkffo.h"
+#include "fifo.h"
+
+#ifdef HASMKFIFO
+int fifo_make(fn,mode) char *fn; int mode; { return mkfifo(fn,mode); }
+#else
+int fifo_make(fn,mode) char *fn; int mode; { return mknod(fn,S_IFIFO | mode,0); }
+#endif
diff --git a/fifo.h b/fifo.h
new file mode 100644 (file)
index 0000000..2c5469b
--- /dev/null
+++ b/fifo.h
@@ -0,0 +1,6 @@
+#ifndef FIFO_H
+#define FIFO_H
+
+extern int fifo_make();
+
+#endif
diff --git a/fifo_make.3 b/fifo_make.3
new file mode 100644 (file)
index 0000000..489233d
--- /dev/null
@@ -0,0 +1,24 @@
+.TH fifo_make 3
+.SH NAME
+fifo_make \- create a named pipe
+.SH SYNTAX
+.B #include <fifo.h>
+
+int \fBfifo_make\fP(\fIfn\fR,\fImode\fR);
+
+char *\fIfn\fR;
+.br
+int \fImode\fR;
+.SH DESCRIPTION
+.B fifo_make
+creates a new named pipe
+with name
+.I fn
+and mode
+.I mode
+(modified by the process umask).
+
+.B fifo_make
+returns 0 on success, -1 on error.
+.SH "SEE ALSO"
+mkfifo(2)
diff --git a/find-systype.sh b/find-systype.sh
new file mode 100644 (file)
index 0000000..16266d3
--- /dev/null
@@ -0,0 +1,144 @@
+# oper-:arch-:syst-:chip-:kern-
+# oper = operating system type; e.g., sunos-4.1.4
+# arch = machine language; e.g., sparc
+# syst = which binaries can run; e.g., sun4
+# chip = chip model; e.g., micro-2-80
+# kern = kernel version; e.g., sun4m
+# dependence: arch --- chip
+#                 \        \
+#          oper --- syst --- kern
+# so, for example, syst is interpreted in light of oper, but chip is not.
+# anyway, no slashes, no extra colons, no uppercase letters.
+# the point of the extra -'s is to ease parsing: can add hierarchies later.
+# e.g., *:i386-*:*:pentium-*:* would handle pentium-100 as well as pentium,
+# and i386-486 (486s do have more instructions, you know) as well as i386.
+# the idea here is to include ALL useful available information.
+
+exec 2>/dev/null
+sys="`uname -s | tr '/:[A-Z]' '..[a-z]'`"
+if [ x"$sys" != x ]
+then
+  unamer="`uname -r | tr /: ..`"
+  unamem="`uname -m | tr /: ..`"
+  unamev="`uname -v | tr /: ..`"
+
+  case "$sys" in
+  bsd.os)
+    # in bsd 4.4, uname -v does not have useful info.
+    # in bsd 4.4, uname -m is arch, not chip.
+    oper="$sys-$unamer"
+    arch="$unamem"
+    syst=""
+    chip="`sysctl -n hw.model`"
+    kern=""
+    ;;
+  freebsd)
+    # see above about bsd 4.4
+    oper="$sys-$unamer"
+    arch="$unamem"
+    syst=""
+    chip="`sysctl -n hw.model`" # hopefully
+    kern=""
+    ;;
+  netbsd)
+    # see above about bsd 4.4
+    oper="$sys-$unamer"
+    arch="$unamem"
+    syst=""
+    chip="`sysctl -n hw.model`" # hopefully
+    kern=""
+    ;;
+  linux)
+    # as in bsd 4.4, uname -v does not have useful info.
+    oper="$sys-$unamer"
+    syst=""
+    chip="$unamem"
+    kern=""
+    case "$chip" in
+    i386|i486|i586|i686)
+      arch="i386"
+      ;;
+    alpha)
+      arch="alpha"
+      ;;
+    esac
+    ;;
+  aix)
+    # naturally IBM has to get uname -r and uname -v backwards. dorks.
+    oper="$sys-$unamev-$unamer"
+    arch="`arch | tr /: ..`"
+    syst=""
+    chip="$unamem"
+    kern=""
+    ;;
+  sunos)
+    oper="$sys-$unamer-$unamev"
+    arch="`(uname -p || mach) | tr /: ..`"
+    syst="`arch | tr /: ..`"
+    chip="$unamem" # this is wrong; is there any way to get the real info?
+    kern="`arch -k | tr /: ..`"
+    ;;
+  unix_sv)
+    oper="$sys-$unamer-$unamev"
+    arch="`uname -m`"
+    syst=""
+    chip="$unamem"
+    kern=""
+    ;;
+  *)
+    oper="$sys-$unamer-$unamev"
+    arch="`arch | tr /: ..`"
+    syst=""
+    chip="$unamem"
+    kern=""
+    ;;
+  esac
+else
+  $CC -c trycpp.c
+  $LD -o trycpp trycpp.o
+  case `./trycpp` in
+  nextstep)
+    oper="nextstep-`hostinfo | sed -n 's/^[    ]*NeXT Mach \([^:]*\):.*$/\1/p'`"
+    arch="`hostinfo | sed -n 's/^Processor type: \(.*\) (.*)$/\1/p' | tr /: ..`"
+    syst=""
+    chip="`hostinfo | sed -n 's/^Processor type: .* (\(.*\))$/\1/p' | tr ' /:' '...'`"
+    kern=""
+    ;;
+  *)
+    oper="unknown"
+    arch=""
+    syst=""
+    chip=""
+    kern=""
+    ;;
+  esac
+  rm -f trycpp.o trycpp
+fi
+
+case "$chip" in
+80486)
+  # let's try to be consistent here. (BSD/OS)
+  chip=i486
+  ;;
+i486DX)
+  # respect the hyphen hierarchy. (FreeBSD)
+  chip=i486-dx
+  ;;
+i486.DX2)
+  # respect the hyphen hierarchy. (FreeBSD)
+  chip=i486-dx2
+  ;;
+Intel.586)
+  # no, you nitwits, there is no such chip. (NeXTStep)
+  chip=pentium
+  ;;
+i586)
+  # no, you nitwits, there is no such chip. (Linux)
+  chip=pentium
+  ;;
+i686)
+  # STOP SAYING THAT! (Linux)
+  chip=ppro
+esac
+
+echo "$oper-:$arch-:$syst-:$chip-:$kern-" | tr ' [A-Z]' '.[a-z]'
diff --git a/fmt.h b/fmt.h
new file mode 100644 (file)
index 0000000..ba2fe16
--- /dev/null
+++ b/fmt.h
@@ -0,0 +1,25 @@
+#ifndef FMT_H
+#define FMT_H
+
+#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */
+#define FMT_LEN ((char *) 0) /* convenient abbreviation */
+
+extern unsigned int fmt_uint();
+extern unsigned int fmt_uint0();
+extern unsigned int fmt_xint();
+extern unsigned int fmt_nbbint();
+extern unsigned int fmt_ushort();
+extern unsigned int fmt_xshort();
+extern unsigned int fmt_nbbshort();
+extern unsigned int fmt_ulong();
+extern unsigned int fmt_xlong();
+extern unsigned int fmt_nbblong();
+
+extern unsigned int fmt_plusminus();
+extern unsigned int fmt_minus();
+extern unsigned int fmt_0x();
+
+extern unsigned int fmt_str();
+extern unsigned int fmt_strn();
+
+#endif
diff --git a/fmt_str.c b/fmt_str.c
new file mode 100644 (file)
index 0000000..48930cf
--- /dev/null
+++ b/fmt_str.c
@@ -0,0 +1,12 @@
+#include "fmt.h"
+
+unsigned int fmt_str(s,t)
+register char *s; register char *t;
+{
+  register unsigned int len;
+  char ch;
+  len = 0;
+  if (s) { while (ch = t[len]) s[len++] = ch; }
+  else while (t[len]) len++;
+  return len;
+}
diff --git a/fmt_strn.c b/fmt_strn.c
new file mode 100644 (file)
index 0000000..3ef58a6
--- /dev/null
@@ -0,0 +1,12 @@
+#include "fmt.h"
+
+unsigned int fmt_strn(s,t,n)
+register char *s; register char *t; register unsigned int n;
+{
+  register unsigned int len;
+  char ch;
+  len = 0;
+  if (s) { while (n-- && (ch = t[len])) s[len++] = ch; }
+  else while (n-- && t[len]) len++;
+  return len;
+}
diff --git a/fmt_uint.c b/fmt_uint.c
new file mode 100644 (file)
index 0000000..8595be8
--- /dev/null
@@ -0,0 +1,6 @@
+#include "fmt.h"
+
+unsigned int fmt_uint(s,u) register char *s; register unsigned int u;
+{
+  register unsigned long l; l = u; return fmt_ulong(s,l);
+}
diff --git a/fmt_uint0.c b/fmt_uint0.c
new file mode 100644 (file)
index 0000000..81705f6
--- /dev/null
@@ -0,0 +1,10 @@
+#include "fmt.h"
+
+unsigned int fmt_uint0(s,u,n) char *s; unsigned int u; unsigned int n;
+{
+  unsigned int len;
+  len = fmt_uint(FMT_LEN,u);
+  while (len < n) { if (s) *s++ = '0'; ++len; }
+  if (s) fmt_uint(s,u);
+  return len;
+}
diff --git a/fmt_ulong.c b/fmt_ulong.c
new file mode 100644 (file)
index 0000000..ab12e8c
--- /dev/null
@@ -0,0 +1,13 @@
+#include "fmt.h"
+
+unsigned int fmt_ulong(s,u) register char *s; register unsigned long u;
+{
+  register unsigned int len; register unsigned long q;
+  len = 1; q = u;
+  while (q > 9) { ++len; q /= 10; }
+  if (s) {
+    s += len;
+    do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */
+  }
+  return len;
+}
diff --git a/fmtqfn.c b/fmtqfn.c
new file mode 100644 (file)
index 0000000..6229b4a
--- /dev/null
+++ b/fmtqfn.c
@@ -0,0 +1,24 @@
+#include "fmtqfn.h"
+#include "fmt.h"
+#include "auto_split.h"
+
+unsigned int fmtqfn(s,dirslash,id,flagsplit)
+char *s;
+char *dirslash;
+unsigned long id;
+int flagsplit;
+{
+ unsigned int len;
+ unsigned int i;
+
+ len = 0;
+ i = fmt_str(s,dirslash); len += i; if (s) s += i;
+ if (flagsplit)
+  {
+   i = fmt_ulong(s,id % auto_split); len += i; if (s) s += i;
+   i = fmt_str(s,"/"); len += i; if (s) s += i;
+  }
+ i = fmt_ulong(s,id); len += i; if (s) s += i;
+ if (s) *s++ = 0; ++len;
+ return len;
+}
diff --git a/fmtqfn.h b/fmtqfn.h
new file mode 100644 (file)
index 0000000..a449aa1
--- /dev/null
+++ b/fmtqfn.h
@@ -0,0 +1,8 @@
+#ifndef FMTQFN_H
+#define FMTQFN_H
+
+extern unsigned int fmtqfn();
+
+#define FMTQFN 40 /* maximum space needed, if len(dirslash) <= 10 */
+
+#endif
diff --git a/forgeries.7 b/forgeries.7
new file mode 100644 (file)
index 0000000..c7517e8
--- /dev/null
@@ -0,0 +1,104 @@
+.TH forgeries 7
+.SH "NAME"
+forgeries \- how easy it is to forge mail
+.SH "SUMMARY"
+An electronic mail message can easily be forged.
+Almost everything in it,
+including the return address,
+is completely under the control of the sender.
+
+An electronic mail message can be manually traced to its origin
+if (1) all system administrators of intermediate machines
+are both cooperative and competent,
+(2) the sender did not break low-level TCP/IP security,
+and
+(3) all intermediate machines are secure.
+
+Users of
+.I cryptography
+can automatically ensure the integrity and secrecy
+of their mail messages, as long as
+the sending and receiving machines are secure.
+.SH "FORGERIES"
+Like postal mail,
+electronic mail can be created entirely at the whim of the sender.
+.BR From ,
+.BR Sender ,
+.BR Return-Path ,
+and
+.BR Message-ID
+can all contain whatever information the sender wants.
+
+For example, if you inject a message through
+.B sendmail
+or
+.B qmail-inject
+or
+.BR SMTP ,
+you can simply type in a
+.B From
+field.
+In fact,
+.B qmail-inject
+lets you set up
+.BR MAILUSER ,
+.BR MAILHOST ,
+and
+.B MAILNAME
+environment variables
+to produce your desired
+.B From
+field on every message.
+.SH "TRACING FORGERIES"
+Like postal mail,
+electronic mail is postmarked when it is sent.
+Each machine that receives an electronic mail message
+adds a
+.B Received
+line to the top.
+
+A modern
+.B Received
+line contains quite a bit of information.
+In conjunction with the machine's logs,
+it lets a competent system administrator
+determine where the machine received the message from,
+as long as the sender did not break low-level TCP/IP security
+or security on that machine.
+
+Large multi-user machines often come with inadequate logging software.
+Fortunately, a system administrator can easily obtain a copy of a
+931/1413/Ident/TAP server, such as
+.BR pidentd .
+Unfortunately,
+many incompetent system administrators fail to do this,
+and are thus unable to figure out which local user
+was responsible for generating a message.
+
+If all intermediate system administrators are competent,
+and the sender did not break machine security or low-level TCP/IP security,
+it is possible to trace a message backwards.
+Unfortunately, some traces are stymied by intermediate system
+administrators who are uncooperative or untrustworthy.
+.SH "CRYPTOGRAPHY"
+The sender of a mail message may place his message into a
+.I cryptographic
+envelope stamped with his seal.
+Strong cryptography guarantees that any two messages with the same seal
+were sent by the same cryptographic entity:
+perhaps a single person, perhaps a group of cooperating people,
+but in any case somebody who knows a secret originally held
+only by the creator of the seal.
+The seal is called a
+.I public key\fR.
+
+Unfortunately, the creator of the seal is often an insecure machine,
+or an untrustworthy central agency,
+but most of the time seals are kept secure.
+
+One popular cryptographic program is
+.BR pgp .
+.SH "SEE ALSO"
+pgp(1),
+identd(8),
+qmail-header(8)
diff --git a/fork.h1 b/fork.h1
new file mode 100644 (file)
index 0000000..b786255
--- /dev/null
+++ b/fork.h1
@@ -0,0 +1,7 @@
+#ifndef FORK_H
+#define FORK_H
+
+extern int fork();
+#define vfork fork
+
+#endif
diff --git a/fork.h2 b/fork.h2
new file mode 100644 (file)
index 0000000..41773b6
--- /dev/null
+++ b/fork.h2
@@ -0,0 +1,7 @@
+#ifndef FORK_H
+#define FORK_H
+
+extern int fork();
+extern int vfork();
+
+#endif
diff --git a/forward.1 b/forward.1
new file mode 100644 (file)
index 0000000..d0a7f95
--- /dev/null
+++ b/forward.1
@@ -0,0 +1,24 @@
+.TH forward 1
+.SH NAME
+forward \- forward new mail to one or more addresses
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |forward
+.I address ...
+.SH DESCRIPTION
+.B forward
+forwards each new mail message to the specified list of addresses.
+It is a simple wrapper around
+.BR qmail-queue .
+It achieves the same results as listing each
+.I address
+separately in
+.BR .qmail ,
+but it is more programmable since
+.I address
+can be constructed on the fly.
+.SH "SEE ALSO"
+dot-qmail(5),
+qmail-command(8),
+qmail-queue(8)
diff --git a/forward.c b/forward.c
new file mode 100644 (file)
index 0000000..de03773
--- /dev/null
+++ b/forward.c
@@ -0,0 +1,58 @@
+#include "sig.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "env.h"
+#include "qmail.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "substdio.h"
+
+void die_success() { _exit(0); }
+void die_perm(s) char *s; { substdio_putsflush(subfderr,s); _exit(100); }
+void die_temp(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); }
+void die_nomem() { die_temp("forward: fatal: out of memory\n"); }
+
+struct qmail qqt;
+
+int mywrite(fd,buf,len) int fd; char *buf; int len;
+{
+ qmail_put(&qqt,buf,len);
+ return len;
+}
+
+substdio ssin;
+substdio ssout;
+char inbuf[SUBSTDIO_INSIZE];
+char outbuf[16];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ char *sender;
+ char *dtline;
+
+ sig_pipeignore();
+
+ sender = env_get("NEWSENDER");
+ if (!sender) die_perm("forward: fatal: NEWSENDER not set\n");
+ dtline = env_get("DTLINE");
+ if (!dtline) die_perm("forward: fatal: DTLINE not set\n");
+
+ if (qmail_open(&qqt) == -1) die_temp("forward: fatal: unable to fork\n");
+ qmail_puts(&qqt,dtline);
+ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+ substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf));
+ if (substdio_copy(&ssout,&ssin) != 0)
+   die_temp("forward: fatal: error while reading message\n");
+ substdio_flush(&ssout);
+
+ qmail_from(&qqt,sender);
+ while (*++argv) qmail_to(&qqt,*argv);
+ switch(qmail_close(&qqt))
+  {
+   case 0: die_success();
+   case QMAIL_TOOLONG: die_perm("forward: fatal: permanent qmail-queue error\n");
+   default: die_temp("forward: fatal: temporary qmail-queue error\n");
+  }
+}
diff --git a/gen_alloc.h b/gen_alloc.h
new file mode 100644 (file)
index 0000000..b94a956
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef GEN_ALLOC_H
+#define GEN_ALLOC_H
+
+#define GEN_ALLOC_typedef(ta,type,field,len,a) \
+  typedef struct ta { type *field; unsigned int len; unsigned int a; } ta;
+
+#endif
diff --git a/gen_allocdefs.h b/gen_allocdefs.h
new file mode 100644 (file)
index 0000000..783a9b1
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef GEN_ALLOC_DEFS_H
+#define GEN_ALLOC_DEFS_H
+
+#define GEN_ALLOC_ready(ta,type,field,len,a,i,n,x,base,ta_ready) \
+int ta_ready(x,n) register ta *x; register unsigned int n; \
+{ register unsigned int i; \
+  if (x->field) { \
+    i = x->a; \
+    if (n > i) { \
+      x->a = base + n + (n >> 3); \
+      if (alloc_re(&x->field,i * sizeof(type),x->a * sizeof(type))) return 1; \
+      x->a = i; return 0; } \
+    return 1; } \
+  x->len = 0; \
+  return !!(x->field = (type *) alloc((x->a = n) * sizeof(type))); }
+
+#define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \
+int ta_rplus(x,n) register ta *x; register unsigned int n; \
+{ register unsigned int i; \
+  if (x->field) { \
+    i = x->a; n += x->len; \
+    if (n > i) { \
+      x->a = base + n + (n >> 3); \
+      if (alloc_re(&x->field,i * sizeof(type),x->a * sizeof(type))) return 1; \
+      x->a = i; return 0; } \
+    return 1; } \
+  x->len = 0; \
+  return !!(x->field = (type *) alloc((x->a = n) * sizeof(type))); }
+
+#define GEN_ALLOC_append(ta,type,field,len,a,i,n,x,base,ta_rplus,ta_append) \
+int ta_append(x,i) register ta *x; register type *i; \
+{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; }
+
+#endif
diff --git a/getln.3 b/getln.3
new file mode 100644 (file)
index 0000000..ffe1953
--- /dev/null
+++ b/getln.3
@@ -0,0 +1,51 @@
+.TH getln 3
+.SH NAME
+getln \- read one line of data
+.SH SYNTAX
+.B #include <getln.h>
+
+int \fBgetln\fP(&\fIss\fR,&\fIsa\fR,&\fImatch\fR,\fIsep\fR);
+
+substdio \fIss\fR;
+.br
+stralloc \fIsa\fR;
+.br
+int \fImatch\fR;
+.br
+int \fIsep\fR;
+.SH DESCRIPTION
+.B getln
+reads a line of characters, terminated by a 
+.I sep
+character,
+from
+.IR ss .
+It returns the line in
+.I sa
+and sets
+.I match
+to 1.
+
+If
+.B getln
+sees end-of-input before it sees
+.IR sep ,
+it returns the partial line in
+.I sa
+and sets
+.I match
+to 0.
+
+.B getln
+normally returns 0.
+If it runs out of memory,
+or encounters an error from
+.IR ss ,
+it returns -1,
+setting
+.B errno
+appropriately.
+.SH "SEE ALSO"
+stralloc(3),
+substdio(3),
+getln2(3)
diff --git a/getln.c b/getln.c
new file mode 100644 (file)
index 0000000..c5cb097
--- /dev/null
+++ b/getln.c
@@ -0,0 +1,20 @@
+#include "substdio.h"
+#include "byte.h"
+#include "stralloc.h"
+#include "getln.h"
+
+int getln(ss,sa,match,sep)
+register substdio *ss;
+register stralloc *sa;
+int *match;
+int sep;
+{
+  char *cont;
+  unsigned int clen;
+
+  if (getln2(ss,sa,&cont,&clen,sep) == -1) return -1;
+  if (!clen) { *match = 0; return 0; }
+  if (!stralloc_catb(sa,cont,clen)) return -1;
+  *match = 1;
+  return 0;
+}
diff --git a/getln.h b/getln.h
new file mode 100644 (file)
index 0000000..cf4f934
--- /dev/null
+++ b/getln.h
@@ -0,0 +1,7 @@
+#ifndef GETLN_H
+#define GETLN_H
+
+extern int getln();
+extern int getln2();
+
+#endif
diff --git a/getln2.3 b/getln2.3
new file mode 100644 (file)
index 0000000..f95105a
--- /dev/null
+++ b/getln2.3
@@ -0,0 +1,64 @@
+.TH getln2 3
+.SH NAME
+getln2 \- read one line of data
+.SH SYNTAX
+.B #include <getln.h>
+
+int \fBgetln2\fP(&\fIss\fR,&\fIsa\fR,&\fIcont\fR,&\fIclen\fR,\fIsep\fR);
+
+substdio \fIss\fR;
+.br
+stralloc \fIsa\fR;
+.br
+char *\fIcont\fR;
+.br
+unsigned int \fIclen\fR;
+.br
+int \fIsep\fR;
+.SH DESCRIPTION
+.B getln2
+reads a line of characters, terminated by a 
+.I sep
+character,
+from
+.IR ss .
+
+The line is returned in two pieces.
+The first piece is stored in
+.IR sa .
+The second piece is
+.IR cont ,
+a pointer to
+.I clen
+characters inside the
+.I ss
+buffer.
+The second piece must be copied somewhere else
+before
+.I ss
+is used again.
+
+If
+.B getln2
+sees end-of-input before it sees
+.IR sep ,
+it sets
+.I clen
+to 0 and does not set
+.IR cont .
+It puts the partial line into
+.IR sa .
+
+.B getln2
+normally returns 0.
+If it runs out of memory,
+or encounters an error from
+.IR ss ,
+it returns -1,
+setting
+.B errno
+appropriately.
+.SH "SEE ALSO"
+stralloc(3),
+substdio(3),
+getln(3)
diff --git a/getln2.c b/getln2.c
new file mode 100644 (file)
index 0000000..9e576e9
--- /dev/null
+++ b/getln2.c
@@ -0,0 +1,31 @@
+#include "substdio.h"
+#include "stralloc.h"
+#include "byte.h"
+#include "getln.h"
+
+int getln2(ss,sa,cont,clen,sep)
+register substdio *ss;
+register stralloc *sa;
+/*@out@*/char **cont;
+/*@out@*/unsigned int *clen;
+int sep;
+{
+  register char *x;
+  register unsigned int i;
+  int n;
+  if (!stralloc_ready(sa,0)) return -1;
+  sa->len = 0;
+  for (;;) {
+    n = substdio_feed(ss);
+    if (n < 0) return -1;
+    if (n == 0) { *clen = 0; return 0; }
+    x = substdio_PEEK(ss);
+    i = byte_chr(x,n,sep);
+    if (i < n) { substdio_SEEK(ss,*clen = i + 1); *cont = x; return 0; }
+    if (!stralloc_readyplus(sa,n)) return -1;
+    i = sa->len;
+    sa->len = i + substdio_get(ss,sa->s + i,n);
+  }
+}
diff --git a/gfrom.c b/gfrom.c
new file mode 100644 (file)
index 0000000..87f9ad4
--- /dev/null
+++ b/gfrom.c
@@ -0,0 +1,10 @@
+#include "str.h"
+#include "gfrom.h"
+
+int gfrom(s,len)
+char *s;
+int len;
+{
+ while ((len > 0) && (*s == '>')) { ++s; --len; }
+ return (len >= 5) && !str_diffn(s,"From ",5);
+}
diff --git a/gfrom.h b/gfrom.h
new file mode 100644 (file)
index 0000000..488193c
--- /dev/null
+++ b/gfrom.h
@@ -0,0 +1,6 @@
+#ifndef GFROM_H
+#define GFROM_H
+
+extern int gfrom();
+
+#endif
diff --git a/headerbody.c b/headerbody.c
new file mode 100644 (file)
index 0000000..91965c0
--- /dev/null
@@ -0,0 +1,87 @@
+#include "stralloc.h"
+#include "substdio.h"
+#include "getln.h"
+#include "hfield.h"
+#include "headerbody.h"
+
+static int getsa(ss,sa,match)
+substdio *ss;
+stralloc *sa;
+int *match;
+{
+ if (!*match) return 0;
+ if (getln(ss,sa,match,'\n') == -1) return -1;
+ if (*match) return 1;
+ if (!sa->len) return 0;
+ if (!stralloc_append(sa,"\n")) return -1;
+ return 1;
+}
+
+static stralloc line = {0};
+static stralloc nextline = {0};
+
+int headerbody(ss,dohf,hdone,dobl)
+substdio *ss;
+void (*dohf)();
+void (*hdone)();
+void (*dobl)();
+{
+ int match;
+ int flaglineok;
+ match = 1;
+ flaglineok = 0;
+ for (;;)
+  {
+   switch(getsa(ss,&nextline,&match))
+    {
+     case -1:
+       return -1;
+     case 0:
+       if (flaglineok) dohf(&line);
+       hdone();
+       /* no message body; could insert blank line here */
+       return 0;
+    }
+   if (flaglineok)
+    {
+     if ((nextline.s[0] == ' ') || (nextline.s[0] == '\t'))
+      {
+       if (!stralloc_cat(&line,&nextline)) return -1;
+       continue;
+      }
+     dohf(&line);
+    }
+   if (nextline.len == 1)
+    {
+     hdone();
+     dobl(&nextline);
+     break;
+    }
+   if (stralloc_starts(&nextline,"From "))
+    {
+     if (!stralloc_copys(&line,"MBOX-Line: ")) return -1;
+     if (!stralloc_cat(&line,&nextline)) return -1;
+    }
+   else
+     if (hfield_valid(nextline.s,nextline.len))
+      {
+       if (!stralloc_copy(&line,&nextline)) return -1;
+      }
+     else
+      {
+       hdone();
+       if (!stralloc_copys(&line,"\n")) return -1;
+       dobl(&line);
+       dobl(&nextline);
+       break;
+      }
+   flaglineok = 1;
+  }
+ for (;;)
+   switch(getsa(ss,&nextline,&match))
+    {
+     case -1: return -1;
+     case 0: return 0;
+     case 1: dobl(&nextline);
+    }
+}
diff --git a/headerbody.h b/headerbody.h
new file mode 100644 (file)
index 0000000..3bb2e2f
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef HEADERBODY_H
+#define HEADERBODY_H
+
+extern int headerbody();
+
+#endif
diff --git a/hfield.c b/hfield.c
new file mode 100644 (file)
index 0000000..38ecebc
--- /dev/null
+++ b/hfield.c
@@ -0,0 +1,124 @@
+#include "hfield.h"
+
+static char *(hname[]) = {
+  "unknown-header"
+, "sender"
+, "from"
+, "reply-to"
+, "to"
+, "cc"
+, "bcc"
+, "date"
+, "message-id"
+, "subject"
+, "resent-sender"
+, "resent-from"
+, "resent-reply-to"
+, "resent-to"
+, "resent-cc"
+, "resent-bcc"
+, "resent-date"
+, "resent-message-id"
+, "return-receipt-to"
+, "errors-to"
+, "apparently-to"
+, "received"
+, "return-path"
+, "delivered-to"
+, "content-length"
+, "content-type"
+, "content-transfer-encoding"
+, "notice-requested-upon-delivery-to"
+, 0
+};
+
+static int hmatch(s,len,t)
+char *s;
+int len;
+char *t;
+{
+ int i;
+ char ch;
+
+ for (i = 0;ch = t[i];++i)
+  {
+   if (i >= len) return 0;
+   if (ch != s[i])
+    {
+     if (ch == '-') return 0;
+     if (ch - 32 != s[i]) return 0;
+    }
+  }
+ for (;;)
+  {
+   if (i >= len) return 0;
+   ch = s[i];
+   if (ch == ':') return 1;
+   if ((ch != ' ') && (ch != '\t')) return 0;
+   ++i;
+  }
+}
+
+int hfield_known(s,len)
+char *s;
+int len;
+{
+ int i;
+ char *t;
+
+ for (i = 1;t = hname[i];++i)
+   if (hmatch(s,len,t))
+     return i;
+ return 0;
+}
+
+int hfield_valid(s,len)
+char *s;
+int len;
+{
+ int i;
+ int j;
+ char ch;
+
+ for (j = 0;j < len;++j)
+   if (s[j] == ':')
+     break;
+ if (j >= len) return 0;
+ while (j)
+  {
+   ch = s[j - 1];
+   if ((ch != ' ') && (ch != '\t'))
+     break;
+   --j;
+  }
+ if (!j) return 0;
+
+ for (i = 0;i < j;++i)
+  {
+   ch = s[i];
+   if (ch <= 32) return 0;
+   if (ch >= 127) return 0;
+  }
+ return 1;
+}
+
+unsigned int hfield_skipname(s,len)
+char *s;
+int len;
+{
+ int i;
+ char ch;
+
+ for (i = 0;i < len;++i)
+   if (s[i] == ':')
+     break;
+ if (i < len) ++i;
+ while (i < len)
+  {
+   ch = s[i];
+   if ((ch != '\t') && (ch != '\n') && (ch != '\r') && (ch != ' '))
+     break;
+   ++i;
+  }
+ return i;
+}
diff --git a/hfield.h b/hfield.h
new file mode 100644 (file)
index 0000000..0f59b82
--- /dev/null
+++ b/hfield.h
@@ -0,0 +1,37 @@
+#ifndef HFIELD_H
+#define HFIELD_H
+
+extern unsigned int hfield_skipname();
+extern int hfield_known();
+extern int hfield_valid();
+
+#define H_SENDER 1
+#define H_FROM 2
+#define H_REPLYTO 3
+#define H_TO 4
+#define H_CC 5
+#define H_BCC 6
+#define H_DATE 7
+#define H_MESSAGEID 8
+#define H_SUBJECT 9
+#define H_R_SENDER 10
+#define H_R_FROM 11
+#define H_R_REPLYTO 12
+#define H_R_TO 13
+#define H_R_CC 14
+#define H_R_BCC 15
+#define H_R_DATE 16
+#define H_R_MESSAGEID 17
+#define H_RETURNRECEIPTTO 18
+#define H_ERRORSTO 19
+#define H_APPARENTLYTO 20
+#define H_RECEIVED 21
+#define H_RETURNPATH 22
+#define H_DELIVEREDTO 23
+#define H_CONTENTLENGTH 24
+#define H_CONTENTTYPE 25
+#define H_CONTENTTRANSFERENCODING 26
+#define H_NOTICEREQUESTEDUPONDELIVERYTO 27
+#define H_NUM 28
+
+#endif
diff --git a/hostname.c b/hostname.c
new file mode 100644 (file)
index 0000000..39c7f87
--- /dev/null
@@ -0,0 +1,17 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "readwrite.h"
+#include "exit.h"
+
+char host[256];
+
+void main()
+{
+ host[0] = 0; /* sigh */
+ gethostname(host,sizeof(host));
+ host[sizeof(host) - 1] = 0;
+ substdio_puts(subfdoutsmall,host);
+ substdio_puts(subfdoutsmall,"\n");
+ substdio_flush(subfdoutsmall);
+ _exit(0);
+}
diff --git a/install.c b/install.c
new file mode 100644 (file)
index 0000000..ffc09a6
--- /dev/null
+++ b/install.c
@@ -0,0 +1,167 @@
+#include "substdio.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "open.h"
+#include "error.h"
+#include "strerr.h"
+#include "byte.h"
+#include "fifo.h"
+
+stralloc target = {0};
+char *to;
+
+#define FATAL "install: fatal: "
+void nomem() { strerr_die2x(111,FATAL,"out of memory"); }
+
+char inbuf[SUBSTDIO_INSIZE];
+char outbuf[SUBSTDIO_OUTSIZE];
+substdio ssin;
+substdio ssout;
+
+void doit(line)
+stralloc *line;
+{
+  char *x;
+  unsigned int xlen;
+  unsigned int i;
+  char *type;
+  char *uidstr;
+  char *gidstr;
+  char *modestr;
+  char *mid;
+  char *name;
+  unsigned long uid;
+  unsigned long gid;
+  unsigned long mode;
+  int fdin;
+  int fdout;
+  unsigned long zlen;
+
+  x = line->s; xlen = line->len;
+
+  type = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  uidstr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  gidstr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  modestr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  mid = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  name = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  if (!stralloc_copys(&target,to)) nomem();
+  if (!stralloc_cats(&target,mid)) nomem();
+  if (!stralloc_cats(&target,name)) nomem();
+  if (!stralloc_0(&target)) nomem();
+
+  uid = -1; if (*uidstr) scan_ulong(uidstr,&uid);
+  gid = -1; if (*gidstr) scan_ulong(gidstr,&gid);
+  scan_8long(modestr,&mode);
+
+  switch(*type) {
+    case 'z':
+      scan_ulong(type + 1,&zlen);
+
+      fdout = open_trunc(target.s);
+      if (fdout == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf));
+
+      while (zlen--)
+       if (substdio_put(&ssout,"",1) == -1)
+         strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+
+      if (substdio_flush(&ssout) == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      if (fsync(fdout) == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      close(fdout);
+      break;
+
+    case 'p':
+      if (fifo_make(target.s,0700) == -1)
+        if (errno != error_exist)
+         strerr_die4sys(111,FATAL,"unable to mkfifo ",target.s,": ");
+      break;
+
+    case 'd':
+      if (mkdir(target.s,0700) == -1)
+        if (errno != error_exist)
+         strerr_die4sys(111,FATAL,"unable to mkdir ",target.s,": ");
+      break;
+
+    case 'c':
+      fdin = open_read(name);
+      if (fdin == -1)
+       strerr_die4sys(111,FATAL,"unable to read ",name,": ");
+      substdio_fdbuf(&ssin,read,fdin,inbuf,sizeof(inbuf));
+
+      fdout = open_trunc(target.s);
+      if (fdout == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      substdio_fdbuf(&ssout,write,fdout,outbuf,sizeof(outbuf));
+
+      switch(substdio_copy(&ssout,&ssin)) {
+       case -2:
+         strerr_die4sys(111,FATAL,"unable to read ",name,": ");
+       case -3:
+         strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      }
+
+      close(fdin);
+      if (substdio_flush(&ssout) == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      if (fsync(fdout) == -1)
+       strerr_die4sys(111,FATAL,"unable to write ",target.s,": ");
+      close(fdout);
+      break;
+
+    default:
+      return;
+  }
+
+  if (chown(target.s,uid,gid) == -1)
+    strerr_die4sys(111,FATAL,"unable to chown ",target.s,": ");
+  if (chmod(target.s,mode) == -1)
+    strerr_die4sys(111,FATAL,"unable to chmod ",target.s,": ");
+}
+
+char buf[256];
+substdio in = SUBSTDIO_FDBUF(read,0,buf,sizeof(buf));
+stralloc line = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  int match;
+
+  umask(077);
+
+  to = argv[1];
+  if (!to) strerr_die2x(100,FATAL,"install: usage: install dir");
+
+  for (;;) {
+    if (getln(&in,&line,&match,'\n') == -1)
+      strerr_die2sys(111,FATAL,"unable to read input: ");
+    doit(&line);
+    if (!match)
+      _exit(0);
+  }
+}
diff --git a/instcheck.c b/instcheck.c
new file mode 100644 (file)
index 0000000..48db531
--- /dev/null
@@ -0,0 +1,120 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "substdio.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "readwrite.h"
+#include "exit.h"
+#include "error.h"
+#include "strerr.h"
+#include "byte.h"
+
+stralloc target = {0};
+char *to;
+
+#define WARNING "instcheck: warning: "
+#define FATAL "instcheck: fatal: "
+void nomem() { strerr_die2x(111,FATAL,"out of memory"); }
+
+void doit(line)
+stralloc *line;
+{
+  struct stat st;
+  char *x;
+  unsigned int xlen;
+  unsigned int i;
+  char *type;
+  char *uidstr;
+  char *gidstr;
+  char *modestr;
+  char *mid;
+  char *name;
+  unsigned long uid;
+  unsigned long gid;
+  unsigned long mode;
+  int ftype;
+
+  x = line->s; xlen = line->len;
+
+  type = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  uidstr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  gidstr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  modestr = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  mid = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  name = x;
+  i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  x[i++] = 0; x += i; xlen -= i;
+
+  if (!stralloc_copys(&target,to)) nomem();
+  if (!stralloc_cats(&target,mid)) nomem();
+  if (!stralloc_cats(&target,name)) nomem();
+  if (!stralloc_0(&target)) nomem();
+
+  uid = -1; if (*uidstr) scan_ulong(uidstr,&uid);
+  gid = -1; if (*gidstr) scan_ulong(gidstr,&gid);
+  scan_8long(modestr,&mode);
+
+  switch(*type) {
+    case 'd': ftype = S_IFDIR; break;
+    case 'c': ftype = S_IFREG; break;
+    case 'z': ftype = S_IFREG; break;
+    case 'p': ftype = S_IFIFO; break;
+    default: return;
+  }
+
+  if (stat(target.s,&st) == -1) {
+    if (errno == error_noent)
+      strerr_warn3(WARNING,target.s," does not exist",0);
+    else
+      strerr_warn4(WARNING,"unable to stat ",target.s,": ",&strerr_sys);
+    return;
+  }
+
+  if ((uid != -1) && (st.st_uid != uid))
+    strerr_warn3(WARNING,target.s," has wrong owner",0);
+  if ((gid != -1) && (st.st_gid != gid))
+    strerr_warn3(WARNING,target.s," has wrong group",0);
+  if ((st.st_mode & 07777) != mode)
+    strerr_warn3(WARNING,target.s," has wrong permissions",0);
+  if ((st.st_mode & S_IFMT) != ftype)
+    strerr_warn3(WARNING,target.s," has wrong type",0);
+}
+
+char buf[256];
+substdio in = SUBSTDIO_FDBUF(read,0,buf,sizeof(buf));
+stralloc line = {0};
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  int match;
+
+  umask(077);
+
+  to = argv[1];
+  if (!to) strerr_die2x(100,FATAL,"instcheck: usage: instcheck dir");
+
+  for (;;) {
+    if (getln(&in,&line,&match,'\n') == -1)
+      strerr_die2sys(111,FATAL,"unable to read input: ");
+    doit(&line);
+    if (!match)
+      _exit(0);
+  }
+}
diff --git a/ip.c b/ip.c
new file mode 100644 (file)
index 0000000..5528ad5
--- /dev/null
+++ b/ip.c
@@ -0,0 +1,53 @@
+#include "fmt.h"
+#include "scan.h"
+#include "ip.h"
+
+unsigned int ip_fmt(s,ip)
+char *s;
+struct ip_address *ip;
+{
+  unsigned int len;
+  unsigned int i;
+  len = 0;
+  i = fmt_ulong(s,(unsigned long) ip->d[0]); len += i; if (s) s += i;
+  i = fmt_str(s,"."); len += i; if (s) s += i;
+  i = fmt_ulong(s,(unsigned long) ip->d[1]); len += i; if (s) s += i;
+  i = fmt_str(s,"."); len += i; if (s) s += i;
+  i = fmt_ulong(s,(unsigned long) ip->d[2]); len += i; if (s) s += i;
+  i = fmt_str(s,"."); len += i; if (s) s += i;
+  i = fmt_ulong(s,(unsigned long) ip->d[3]); len += i; if (s) s += i;
+  return len;
+}
+
+unsigned int ip_scan(s,ip)
+char *s;
+struct ip_address *ip;
+{
+  unsigned int i;
+  unsigned int len;
+  unsigned long u;
+  len = 0;
+  i = scan_ulong(s,&u); if (!i) return 0; ip->d[0] = u; s += i; len += i;
+  if (*s != '.') return 0; ++s; ++len;
+  i = scan_ulong(s,&u); if (!i) return 0; ip->d[1] = u; s += i; len += i;
+  if (*s != '.') return 0; ++s; ++len;
+  i = scan_ulong(s,&u); if (!i) return 0; ip->d[2] = u; s += i; len += i;
+  if (*s != '.') return 0; ++s; ++len;
+  i = scan_ulong(s,&u); if (!i) return 0; ip->d[3] = u; s += i; len += i;
+  return len;
+}
+
+unsigned int ip_scanbracket(s,ip)
+char *s;
+struct ip_address *ip;
+{
+  unsigned int len;
+  if (*s != '[') return 0;
+  len = ip_scan(s + 1,ip);
+  if (!len) return 0;
+  if (s[len + 1] != ']') return 0;
+  return len + 2;
+}
diff --git a/ip.h b/ip.h
new file mode 100644 (file)
index 0000000..b99002f
--- /dev/null
+++ b/ip.h
@@ -0,0 +1,11 @@
+#ifndef IP_H
+#define IP_H
+
+struct ip_address { unsigned char d[4]; } ;
+
+extern unsigned int ip_fmt();
+#define IPFMT 19
+extern unsigned int ip_scan();
+extern unsigned int ip_scanbracket();
+
+#endif
diff --git a/ipalloc.c b/ipalloc.c
new file mode 100644 (file)
index 0000000..81792d5
--- /dev/null
+++ b/ipalloc.c
@@ -0,0 +1,7 @@
+#include "alloc.h"
+#include "gen_allocdefs.h"
+#include "ip.h"
+#include "ipalloc.h"
+
+GEN_ALLOC_readyplus(ipalloc,struct ip_mx,ix,len,a,i,n,x,10,ipalloc_readyplus)
+GEN_ALLOC_append(ipalloc,struct ip_mx,ix,len,a,i,n,x,10,ipalloc_readyplus,ipalloc_append)
diff --git a/ipalloc.h b/ipalloc.h
new file mode 100644 (file)
index 0000000..ad61475
--- /dev/null
+++ b/ipalloc.h
@@ -0,0 +1,14 @@
+#ifndef IPALLOC_H
+#define IPALLOC_H
+
+#include "ip.h"
+
+struct ip_mx { struct ip_address ip; int pref; } ;
+
+#include "gen_alloc.h"
+
+GEN_ALLOC_typedef(ipalloc,struct ip_mx,ix,len,a)
+extern int ipalloc_readyplus();
+extern int ipalloc_append();
+
+#endif
diff --git a/ipme.c b/ipme.c
new file mode 100644 (file)
index 0000000..1f190b7
--- /dev/null
+++ b/ipme.c
@@ -0,0 +1,95 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#ifndef SIOCGIFCONF /* whatever works */
+#include <sys/sockio.h>
+#endif
+#include "hassalen.h"
+#include "byte.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "stralloc.h"
+#include "ipme.h"
+
+static int ipmeok = 0;
+ipalloc ipme = {0};
+
+int ipme_is(ip)
+struct ip_address *ip;
+{
+  int i;
+  if (ipme_init() != 1) return -1;
+  for (i = 0;i < ipme.len;++i)
+    if (byte_equal(&ipme.ix[i].ip,4,ip))
+      return 1;
+  return 0;
+}
+
+static stralloc buf = {0};
+
+int ipme_init()
+{
+  struct ifconf ifc;
+  char *x;
+  struct ifreq *ifr;
+  struct sockaddr_in *sin;
+  int len;
+  int s;
+  struct ip_mx ix;
+  if (ipmeok) return 1;
+  if (!ipalloc_readyplus(&ipme,0)) return 0;
+  ipme.len = 0;
+  ix.pref = 0;
+  if ((s = socket(AF_INET,SOCK_STREAM,0)) == -1) return -1;
+  len = 256;
+  for (;;) {
+    if (!stralloc_ready(&buf,len)) { close(s); return 0; }
+    buf.len = 0;
+    ifc.ifc_buf = buf.s;
+    ifc.ifc_len = len;
+    if (ioctl(s,SIOCGIFCONF,&ifc) >= 0) /* > is for System V */
+      if (ifc.ifc_len + sizeof(*ifr) + 64 < len) { /* what a stupid interface */
+        buf.len = ifc.ifc_len;
+        break;
+      }
+    if (len > 200000) { close(s); return -1; }
+    len += 100 + (len >> 2);
+  }
+  x = buf.s;
+  while (x < buf.s + buf.len) {
+    ifr = (struct ifreq *) x;
+#ifdef HASSALEN
+    len = sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;
+    if (len < sizeof(*ifr))
+      len = sizeof(*ifr);
+    if (ifr->ifr_addr.sa_family == AF_INET) {
+      sin = (struct sockaddr_in *) &ifr->ifr_addr;
+      byte_copy(&ix.ip,4,&sin->sin_addr);
+      if (ioctl(s,SIOCGIFFLAGS,x) == 0)
+        if (ifr->ifr_flags & IFF_UP)
+          if (!ipalloc_append(&ipme,&ix)) { close(s); return 0; }
+    }
+#else
+    len = sizeof(*ifr);
+    if (ioctl(s,SIOCGIFFLAGS,x) == 0)
+      if (ifr->ifr_flags & IFF_UP)
+        if (ioctl(s,SIOCGIFADDR,x) == 0)
+         if (ifr->ifr_addr.sa_family == AF_INET) {
+           sin = (struct sockaddr_in *) &ifr->ifr_addr;
+           byte_copy(&ix.ip,4,&sin->sin_addr);
+           if (!ipalloc_append(&ipme,&ix)) { close(s); return 0; }
+         }
+#endif
+    x += len;
+  }
+  close(s);
+  ipmeok = 1;
+  return 1;
+}
diff --git a/ipme.h b/ipme.h
new file mode 100644 (file)
index 0000000..5bbf515
--- /dev/null
+++ b/ipme.h
@@ -0,0 +1,12 @@
+#ifndef IPME_H
+#define IPME_H
+
+#include "ip.h"
+#include "ipalloc.h"
+
+extern ipalloc ipme;
+
+extern int ipme_init();
+extern int ipme_is();
+
+#endif
diff --git a/ipmeprint.c b/ipmeprint.c
new file mode 100644 (file)
index 0000000..1ef56e3
--- /dev/null
@@ -0,0 +1,24 @@
+#include "subfd.h"
+#include "substdio.h"
+#include "ip.h"
+#include "ipme.h"
+#include "exit.h"
+
+char temp[IPFMT];
+
+void main()
+{
+ int j;
+ switch(ipme_init())
+  {
+   case 0: substdio_putsflush(subfderr,"out of memory\n"); _exit(111);
+   case -1: substdio_putsflush(subfderr,"hard error\n"); _exit(100);
+  }
+ for (j = 0;j < ipme.len;++j)
+  {
+   substdio_put(subfdout,temp,ip_fmt(temp,&ipme.ix[j].ip));
+   substdio_puts(subfdout,"\n");
+  }
+ substdio_flush(subfdout);
+ _exit(0);
+}
diff --git a/lock.h b/lock.h
new file mode 100644 (file)
index 0000000..a7dee41
--- /dev/null
+++ b/lock.h
@@ -0,0 +1,8 @@
+#ifndef LOCK_H
+#define LOCK_H
+
+extern int lock_ex();
+extern int lock_un();
+extern int lock_exnb();
+
+#endif
diff --git a/lock_ex.c b/lock_ex.c
new file mode 100644 (file)
index 0000000..a3351c9
--- /dev/null
+++ b/lock_ex.c
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include "hasflock.h"
+#include "lock.h"
+
+#ifdef HASFLOCK
+int lock_ex(fd) int fd; { return flock(fd,LOCK_EX); }
+#else
+int lock_ex(fd) int fd; { return lockf(fd,1,0); }
+#endif
diff --git a/lock_exnb.c b/lock_exnb.c
new file mode 100644 (file)
index 0000000..5d2a14a
--- /dev/null
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include "hasflock.h"
+#include "lock.h"
+
+#ifdef HASFLOCK
+int lock_exnb(fd) int fd; { return flock(fd,LOCK_EX | LOCK_NB); }
+#else
+int lock_exnb(fd) int fd; { return lockf(fd,2,0); }
+#endif
diff --git a/lock_un.c b/lock_un.c
new file mode 100644 (file)
index 0000000..16e2f17
--- /dev/null
+++ b/lock_un.c
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/file.h>
+#include <fcntl.h>
+#include "hasflock.h"
+#include "lock.h"
+
+#ifdef HASFLOCK
+int lock_un(fd) int fd; { return flock(fd,LOCK_UN); }
+#else
+int lock_un(fd) int fd; { return lockf(fd,0,0); }
+#endif
diff --git a/maildir.5 b/maildir.5
new file mode 100644 (file)
index 0000000..54ddb89
--- /dev/null
+++ b/maildir.5
@@ -0,0 +1,239 @@
+.TH maildir 5
+.SH "NAME"
+maildir \- directory for incoming mail messages
+.SH "INTRODUCTION"
+.I maildir
+is a structure for
+directories of incoming mail messages.
+It solves the reliability problems that plague
+.I mbox
+files and
+.I mh
+folders.
+.SH "RELIABILITY ISSUES"
+A machine may crash while it is delivering a message.
+For both
+.I mbox
+files and
+.I mh
+folders this means that the message will be silently truncated.
+Even worse: for
+.I mbox
+format, if the message is truncated in the middle of a line,
+it will be silently joined to the next message.
+The mail transport agent will try again later to deliver the message,
+but it is unacceptable that a corrupted message should show up at all.
+In
+.IR maildir ,
+every message is guaranteed complete upon delivery.
+
+A machine may have two programs simultaneously delivering mail
+to the same user.
+The
+.I mbox
+and
+.I mh
+formats require the programs to update a single central file.
+If the programs do not use some locking mechanism,
+the central file will be corrupted.
+There are several
+.I mbox
+and
+.I mh
+locking mechanisms,
+none of which work portably and reliably.
+In contrast, in
+.IR maildir ,
+no locks are ever necessary.
+Different delivery processes never touch the same file.
+
+A user may try to delete messages from his mailbox at the same
+moment that the machine delivers a new message.
+For
+.I mbox
+and
+.I mh
+formats, the user's mail-reading program must know
+what locking mechanism the mail-delivery programs use.
+In contrast, in
+.IR maildir ,
+any delivered message
+can be safely updated or deleted by a mail-reading program.
+
+Many sites use Sun's 
+.B Network F\fPa\fBil\fPur\fBe System
+(NFS),
+presumably because the operating system vendor does not offer
+anything else.
+NFS exacerbates all of the above problems.
+Some NFS implementations don't provide
+.B any
+reliable locking mechanism.
+With 
+.I mbox
+and
+.I mh
+formats,
+if two machines deliver mail to the same user,
+or if a user reads mail anywhere except the delivery machine,
+the user's mail is at risk.
+.I maildir
+works without trouble over NFS.
+.SH "THE MAILDIR STRUCTURE"
+A directory in
+.I maildir
+format has three subdirectories,
+all on the same filesystem:
+.BR tmp ,
+.BR new ,
+and
+.BR cur .
+
+Each file in
+.B new
+is a newly delivered mail message.
+The modification time of the file is the delivery date of the message.
+The message is delivered
+.I without
+an extra UUCP-style
+.B From_
+line,
+.I without
+any
+.B >From
+quoting,
+and
+.I without
+an extra blank line at the end.
+The message is normally in RFC 822 format,
+starting with a
+.B Return-Path
+line and a
+.B Delivered-To
+line,
+but it could contain arbitrary binary data.
+It might not even end with a newline.
+
+Files in
+.B cur
+are just like files in
+.BR new .
+The big difference is that files in
+.B cur
+are no longer new mail:
+they have been seen by the user's mail-reading program.
+.SH "HOW A MESSAGE IS DELIVERED"
+The
+.B tmp
+directory is used to ensure reliable delivery,
+as discussed here.
+
+A program delivers a mail message in six steps.
+First, it
+.B chdir()\fPs
+to the
+.I maildir
+directory.
+Second, it 
+.B stat()s
+the name
+.BR tmp/\fItime.pid.host ,
+where
+.I time
+is the number of seconds since the beginning of 1970 GMT,
+.I pid
+is the program's process ID,
+and
+.I host
+is the host name.
+Third, if
+.B stat()
+returned anything other than ENOENT,
+the program sleeps for two seconds, updates
+.IR time ,
+and tries the
+.B stat()
+again, a limited number of times.
+Fourth, the program
+creates
+.BR tmp/\fItime.pid.host .
+Fifth, the program
+.I NFS-writes
+the message to the file.
+Sixth, the program
+.BR link() s
+the file to
+.BR new/\fItime.pid.host .
+At that instant the message has been successfully delivered.
+
+The delivery program is required to start a 24-hour timer before
+creating
+.BR tmp/\fItime.pid.host ,
+and to abort the delivery
+if the timer expires.
+Upon error, timeout, or normal completion,
+the delivery program may attempt to
+.B unlink()
+.BR tmp/\fItime.pid.host .
+
+.I NFS-writing
+means
+(1) as usual, checking the number of bytes returned from each
+.B write()
+call;
+(2) calling
+.B fsync()
+and checking its return value;
+(3) calling
+.B close()
+and checking its return value.
+(Standard NFS implementations handle
+.B fsync()
+incorrectly
+but make up for it by abusing
+.BR close() .)
+.SH "HOW A MESSAGE IS READ"
+A mail reader operates as follows.
+
+It looks through the
+.B new
+directory for new messages.
+Say there is a new message,
+.BR new/\fIunique .
+The reader may freely display the contents of
+.BR new/\fIunique ,
+delete
+.BR new/\fIunique ,
+or rename
+.B new/\fIunique
+as
+.BR cur/\fIunique:info .
+See
+.B http://pobox.com/~djb/maildir.html
+for the meaning of
+.IR info .
+
+The reader is also expected to look through the
+.B tmp
+directory and to clean up any old files found there.
+A file in
+.B tmp
+may be safely removed if it
+has not been accessed in 36 hours.
+
+It is a good idea for readers to skip all filenames in
+.B new
+and
+.B cur
+starting with a dot.
+Other than this, readers should not attempt to parse filenames.
+.SH "ENVIRONMENT VARIABLES"
+Mail readers supporting
+.I maildir
+use the
+.B MAILDIR
+environment variable
+as the name of the user's primary mail directory.
+.SH "SEE ALSO"
+mbox(5),
+qmail-local(8)
diff --git a/maildir.c b/maildir.c
new file mode 100644 (file)
index 0000000..efbd3d4
--- /dev/null
+++ b/maildir.c
@@ -0,0 +1,108 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "prioq.h"
+#include "env.h"
+#include "stralloc.h"
+#include "direntry.h"
+#include "datetime.h"
+#include "now.h"
+#include "str.h"
+#include "maildir.h"
+
+struct strerr maildir_chdir_err;
+struct strerr maildir_scan_err;
+
+int maildir_chdir()
+{
+ char *maildir;
+ maildir = env_get("MAILDIR");
+ if (!maildir)
+   STRERR(-1,maildir_chdir_err,"MAILDIR not set")
+ if (chdir(maildir) == -1)
+   STRERR_SYS3(-1,maildir_chdir_err,"unable to chdir to ",maildir,": ")
+ return 0;
+}
+
+void maildir_clean(tmpname)
+stralloc *tmpname;
+{
+ DIR *dir;
+ direntry *d;
+ datetime_sec time;
+ struct stat st;
+
+ time = now();
+
+ dir = opendir("tmp");
+ if (!dir) return;
+
+ while (d = readdir(dir))
+  {
+   if (d->d_name[0] == '.') continue;
+   if (!stralloc_copys(tmpname,"tmp/")) break;
+   if (!stralloc_cats(tmpname,d->d_name)) break;
+   if (!stralloc_0(tmpname)) break;
+   if (stat(tmpname->s,&st) == 0)
+     if (time > st.st_atime + 129600)
+       unlink(tmpname->s);
+  }
+ closedir(dir);
+}
+
+static int append(pq,filenames,subdir,time)
+prioq *pq;
+stralloc *filenames;
+char *subdir;
+datetime_sec time;
+{
+ DIR *dir;
+ direntry *d;
+ struct prioq_elt pe;
+ unsigned int pos;
+ struct stat st;
+
+ dir = opendir(subdir);
+ if (!dir)
+   STRERR_SYS3(-1,maildir_scan_err,"unable to scan $MAILDIR/",subdir,": ")
+
+ while (d = readdir(dir))
+  {
+   if (d->d_name[0] == '.') continue;
+   pos = filenames->len;
+   if (!stralloc_cats(filenames,subdir)) break;
+   if (!stralloc_cats(filenames,"/")) break;
+   if (!stralloc_cats(filenames,d->d_name)) break;
+   if (!stralloc_0(filenames)) break;
+   if (stat(filenames->s + pos,&st) == 0)
+     if (st.st_mtime < time) /* don't want to mix up the order */
+      {
+       pe.dt = st.st_mtime;
+       pe.id = pos;
+       if (!prioq_insert(pq,&pe)) break;
+      }
+  }
+
+ closedir(dir);
+ if (d) STRERR_SYS3(-1,maildir_scan_err,"unable to read $MAILDIR/",subdir,": ")
+ return 0;
+}
+
+int maildir_scan(pq,filenames,flagnew,flagcur)
+prioq *pq;
+stralloc *filenames;
+int flagnew;
+int flagcur;
+{
+ struct prioq_elt pe;
+ datetime_sec time;
+ int r;
+
+ if (!stralloc_copys(filenames,"")) return 0;
+ while (prioq_min(pq,&pe)) prioq_delmin(pq);
+
+ time = now();
+
+ if (flagnew) if (append(pq,filenames,"new",time) == -1) return -1;
+ if (flagcur) if (append(pq,filenames,"cur",time) == -1) return -1;
+ return 0;
+}
diff --git a/maildir.h b/maildir.h
new file mode 100644 (file)
index 0000000..7dd8826
--- /dev/null
+++ b/maildir.h
@@ -0,0 +1,12 @@
+#ifndef MAILDIR_H
+#define MAILDIR_H
+
+#include "strerr.h"
+extern struct strerr maildir_chdir_err;
+extern struct strerr maildir_scan_err;
+
+extern int maildir_chdir();
+extern void maildir_clean();
+extern int maildir_scan();
+
+#endif
diff --git a/maildir2mbox.1 b/maildir2mbox.1
new file mode 100644 (file)
index 0000000..31423d9
--- /dev/null
@@ -0,0 +1,53 @@
+.TH maildir2mbox 1
+.SH NAME
+maildir2mbox \- move mail from a maildir to an mbox
+.SH SYNOPSIS
+.B maildir2mbox
+.SH DESCRIPTION
+.B maildir2mbox
+moves mail from a
+.IR maildir -format
+directory to an
+.IR mbox -format
+file.
+
+You must supply three environment variables to
+.BR maildir2mbox :
+.B MAILDIR
+is the name of your
+.I maildir
+directory;
+.B MAIL
+is the name of your
+.I mbox
+file;
+and
+.B MAILTMP
+is a temporary file that
+.B maildir2mbox
+can overwrite.
+.B MAILTMP
+and
+.B MAIL
+must be on the same filesystem.
+
+.B maildir2mbox
+is reliable:
+it will not remove messages
+from
+.B MAILDIR
+until the messages have been successfully appended to
+.BR MAIL .
+
+.B maildir2mbox
+locks
+.B MAIL
+to protect against simultaneous access by a mail reader.
+This locking system does not protect against simultaneous access
+by another
+.BR maildir2mbox ;
+you should run only one
+.B maildir2mbox
+at a time.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/maildir2mbox.c b/maildir2mbox.c
new file mode 100644 (file)
index 0000000..7364441
--- /dev/null
@@ -0,0 +1,162 @@
+#include "readwrite.h"
+#include "prioq.h"
+#include "env.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "substdio.h"
+#include "getln.h"
+#include "error.h"
+#include "open.h"
+#include "lock.h"
+#include "gfrom.h"
+#include "str.h"
+#include "exit.h"
+#include "myctime.h"
+#include "maildir.h"
+
+char *mbox;
+char *mboxtmp;
+
+stralloc filenames = {0};
+prioq pq = {0};
+prioq pq2 = {0};
+
+stralloc line = {0};
+
+stralloc ufline = {0};
+
+char inbuf[SUBSTDIO_INSIZE];
+char outbuf[SUBSTDIO_OUTSIZE];
+
+#define FATAL "maildir2mbox: fatal: "
+#define WARNING "maildir2mbox: warning: "
+
+void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); }
+
+void main()
+{
+ substdio ssin;
+ substdio ssout;
+ struct prioq_elt pe;
+ int fdoldmbox;
+ int fdnewmbox;
+ int fd;
+ int match;
+ int fdlock;
+
+ umask(077);
+
+ mbox = env_get("MAIL");
+ if (!mbox) strerr_die2x(111,FATAL,"MAIL not set");
+ mboxtmp = env_get("MAILTMP");
+ if (!mboxtmp) strerr_die2x(111,FATAL,"MAILTMP not set");
+
+ if (maildir_chdir() == -1)
+   strerr_die1(111,FATAL,&maildir_chdir_err);
+ maildir_clean(&filenames);
+ if (maildir_scan(&pq,&filenames,1,1) == -1)
+   strerr_die1(111,FATAL,&maildir_scan_err);
+
+ if (!prioq_min(&pq,&pe)) _exit(0); /* nothing new */
+
+ fdlock = open_append(mbox);
+ if (fdlock == -1)
+   strerr_die4sys(111,FATAL,"unable to lock ",mbox,": ");
+ if (lock_ex(fdlock) == -1)
+   strerr_die4sys(111,FATAL,"unable to lock ",mbox,": ");
+
+ fdoldmbox = open_read(mbox);
+ if (fdoldmbox == -1)
+   strerr_die4sys(111,FATAL,"unable to read ",mbox,": ");
+
+ fdnewmbox = open_trunc(mboxtmp);
+ if (fdnewmbox == -1)
+   strerr_die4sys(111,FATAL,"unable to create ",mboxtmp,": ");
+
+ substdio_fdbuf(&ssin,read,fdoldmbox,inbuf,sizeof(inbuf));
+ substdio_fdbuf(&ssout,write,fdnewmbox,outbuf,sizeof(outbuf));
+
+ switch(substdio_copy(&ssout,&ssin))
+  {
+   case -2: strerr_die4sys(111,FATAL,"unable to read ",mbox,": ");
+   case -3: strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+  }
+
+ while (prioq_min(&pq,&pe))
+  {
+   prioq_delmin(&pq);
+   if (!prioq_insert(&pq2,&pe)) die_nomem();
+
+   fd = open_read(filenames.s + pe.id);
+   if (fd == -1)
+     strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": ");
+   substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+
+   if (getln(&ssin,&line,&match,'\n') != 0)
+     strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": ");
+   
+   if (!stralloc_copys(&ufline,"From XXX ")) die_nomem();
+   if (match)
+     if (stralloc_starts(&line,"Return-Path: <"))
+      {
+       if (line.s[14] == '>')
+       {
+         if (!stralloc_copys(&ufline,"From MAILER-DAEMON ")) die_nomem();
+       }
+       else
+       {
+        int i;
+         if (!stralloc_ready(&ufline,line.len)) die_nomem();
+         if (!stralloc_copys(&ufline,"From ")) die_nomem();
+        for (i = 14;i < line.len - 2;++i)
+          if ((line.s[i] == ' ') || (line.s[i] == '\t'))
+            ufline.s[ufline.len++] = '-';
+          else
+            ufline.s[ufline.len++] = line.s[i];
+         if (!stralloc_cats(&ufline," ")) die_nomem();
+       }
+      }
+   if (!stralloc_cats(&ufline,myctime(pe.dt))) die_nomem();
+   if (substdio_put(&ssout,ufline.s,ufline.len) == -1)
+     strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+
+   while (match && line.len)
+    {
+     if (gfrom(line.s,line.len))
+       if (substdio_puts(&ssout,">") == -1)
+         strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+     if (substdio_put(&ssout,line.s,line.len) == -1)
+       strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+     if (!match)
+      {
+       if (substdio_puts(&ssout,"\n") == -1)
+         strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+       break;
+      }
+     if (getln(&ssin,&line,&match,'\n') != 0)
+       strerr_die4sys(111,FATAL,"unable to read $MAILDIR/",filenames.s + pe.id,": ");
+    }
+   if (substdio_puts(&ssout,"\n"))
+     strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+
+   close(fd);
+  }
+
+ if (substdio_flush(&ssout) == -1)
+   strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+ if (fsync(fdnewmbox) == -1)
+   strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+ if (close(fdnewmbox) == -1) /* NFS dorks */
+   strerr_die4sys(111,FATAL,"unable to write to ",mboxtmp,": ");
+ if (rename(mboxtmp,mbox) == -1)
+   strerr_die6(111,FATAL,"unable to move ",mboxtmp," to ",mbox,": ",&strerr_sys);
+ while (prioq_min(&pq2,&pe))
+  {
+   prioq_delmin(&pq2);
+   if (unlink(filenames.s + pe.id) == -1)
+     strerr_warn4(WARNING,"$MAILDIR/",filenames.s + pe.id," will be delivered twice; unable to unlink: ",&strerr_sys);
+  }
+
+ _exit(0);
+}
diff --git a/maildirmake.1 b/maildirmake.1
new file mode 100644 (file)
index 0000000..e5bd042
--- /dev/null
@@ -0,0 +1,15 @@
+.TH maildirmake 1
+.SH NAME
+maildirmake \- create a maildir for incoming mail
+.SH SYNOPSIS
+.B maildirmake
+.I dir
+.SH DESCRIPTION
+.B maildirmake
+makes a new directory,
+.IR dir ,
+in
+.B maildir
+format.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/maildirmake.c b/maildirmake.c
new file mode 100644 (file)
index 0000000..f9822ec
--- /dev/null
@@ -0,0 +1,22 @@
+#include "subfd.h"
+#include "substdio.h"
+#include "error.h"
+#include "exit.h"
+
+void die(s) char *s; { substdio_putsflush(subfderr,s); _exit(111); }
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ umask(077);
+ if (!argv[1]) die("usage: maildirmake name\n");
+ if (mkdir(argv[1],0700))
+   if (errno == error_exist) die("fatal: directory already exists\n");
+   else die("fatal: unable to mkdir\n");
+ if (chdir(argv[1])) die("fatal: unable to chdir\n");
+ if (mkdir("tmp",0700)) die("fatal: unable to make tmp/ subdir\n");
+ if (mkdir("new",0700)) die("fatal: unable to make new/ subdir\n");
+ if (mkdir("cur",0700)) die("fatal: unable to make cur/ subdir\n");
+ _exit(0);
+}
diff --git a/maildirwatch.1 b/maildirwatch.1
new file mode 100644 (file)
index 0000000..d1d70a0
--- /dev/null
@@ -0,0 +1,23 @@
+.TH maildirwatch 1
+.SH NAME
+maildirwatch \- look for new mail in a maildir
+.SH SYNOPSIS
+.B maildirwatch
+.SH DESCRIPTION
+.B maildirwatch
+watches your
+.I maildir
+for new mail.
+You must supply a
+.B MAILDIR
+environment variable
+with the name of your
+.I maildir
+directory.
+
+.B maildirwatch
+prints a new mail summary twice per minute.
+It is designed to run inside a (VT100-compatible) window;
+it clears the window before each summary.
+.SH "SEE ALSO"
+maildir(5)
diff --git a/maildirwatch.c b/maildirwatch.c
new file mode 100644 (file)
index 0000000..40d8322
--- /dev/null
@@ -0,0 +1,125 @@
+#include "getln.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "prioq.h"
+#include "stralloc.h"
+#include "str.h"
+#include "exit.h"
+#include "hfield.h"
+#include "readwrite.h"
+#include "open.h"
+#include "headerbody.h"
+#include "maildir.h"
+
+#define FATAL "maildirwatch: fatal: "
+
+void die_nomem() { strerr_die2x(111,FATAL,"out of memory"); }
+
+stralloc recipient = {0};
+stralloc sender = {0};
+stralloc fromline = {0};
+stralloc text = {0};
+
+void addtext(s,n) char *s; int n;
+{
+ if (!stralloc_catb(&text,s,n)) die_nomem();
+ if (text.len > 158) text.len = 158;
+}
+void dobody(h) stralloc *h; { addtext(h->s,h->len); }
+void doheader(h) stralloc *h;
+{
+ int i;
+ switch(hfield_known(h->s,h->len))
+  {
+   case H_SUBJECT:
+     i = hfield_skipname(h->s,h->len);
+     addtext(h->s + i,h->len - i);
+     break;
+   case H_DELIVEREDTO:
+     i = hfield_skipname(h->s,h->len);
+     if (i < h->len)
+       if (!stralloc_copyb(&recipient,h->s + i,h->len - i - 1)) die_nomem();
+     break;
+   case H_RETURNPATH:
+     i = hfield_skipname(h->s,h->len);
+     if (i < h->len)
+       if (!stralloc_copyb(&sender,h->s + i,h->len - i - 1)) die_nomem();
+     break;
+   case H_FROM:
+     if (!stralloc_copyb(&fromline,h->s,h->len - 1)) die_nomem();
+     break;
+  }
+}
+void finishheader() { ; }
+
+stralloc filenames = {0};
+prioq pq = {0};
+
+char inbuf[SUBSTDIO_INSIZE];
+substdio ssin;
+
+void main()
+{
+ struct prioq_elt pe;
+ int fd;
+ int i;
+
+ if (maildir_chdir() == -1)
+   strerr_die1(111,FATAL,&maildir_chdir_err);
+
+ for (;;)
+  {
+   maildir_clean(&filenames);
+   if (maildir_scan(&pq,&filenames,1,0) == -1)
+     strerr_die1(111,FATAL,&maildir_scan_err);
+
+   substdio_putsflush(subfdout,"\033[;H\033[;J");
+
+   while (prioq_min(&pq,&pe))
+    {
+     prioq_delmin(&pq);
+
+     fd = open_read(filenames.s + pe.id);
+     if (fd == -1) continue;
+     substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+
+     if (!stralloc_copys(&sender,"?")) die_nomem();
+     if (!stralloc_copys(&recipient,"?")) die_nomem();
+     if (!stralloc_copys(&fromline,"")) die_nomem();
+     if (!stralloc_copys(&text,"")) die_nomem();
+     if (headerbody(&ssin,doheader,finishheader,dobody) == -1)
+       strerr_die2x(111,FATAL,"trouble reading new message");
+
+     for (i = 0;i < fromline.len;++i)
+       if ((fromline.s[i] < 32) || (fromline.s[i] > 126))
+         fromline.s[i] = '/';
+     for (i = 0;i < sender.len;++i)
+       if ((sender.s[i] < 32) || (sender.s[i] > 126))
+         sender.s[i] = '?';
+     for (i = 0;i < recipient.len;++i)
+       if ((recipient.s[i] < 32) || (recipient.s[i] > 126))
+         recipient.s[i] = '?';
+     for (i = 0;i < text.len;++i)
+       if ((text.s[i] < 32) || (text.s[i] > 126))
+         text.s[i] = '/';
+     substdio_puts(subfdout,"FROM ");
+     substdio_put(subfdout,sender.s,sender.len);
+     substdio_puts(subfdout," TO <");
+     substdio_put(subfdout,recipient.s,recipient.len);
+     substdio_puts(subfdout,">\n");
+     if (fromline.len)
+      {
+       substdio_puts(subfdout,"\033[1m");
+       substdio_put(subfdout,fromline.s,fromline.len);
+       substdio_puts(subfdout,"\033[0m\n");
+      }
+     substdio_put(subfdout,text.s,text.len);
+     substdio_puts(subfdout,"\n\n");
+
+     close(fd);
+    }
+
+   substdio_flush(subfdout);
+   sleep(30);
+  }
+}
diff --git a/mailsubj.1 b/mailsubj.1
new file mode 100644 (file)
index 0000000..687b8e8
--- /dev/null
@@ -0,0 +1,38 @@
+.TH mailsubj 1
+.SH NAME
+mailsubj \- send a mail message with a subject line
+.SH SYNOPSIS
+.B mailsubj
+.I subject
+.I recip ...
+.SH DESCRIPTION
+.B mailsubj
+inserts
+.I subject
+and the list of
+.IR recip s
+into a mail message:
+
+.EX
+   Subject: subject
+.br
+   To: recip ...
+.br
+
+.br
+   body
+.EE
+
+.B mailsubj
+reads the body of the message from its standard input.
+Then it sends the message.
+
+Note that
+.I subject
+and
+.I recip
+must be quoted properly for the message header.
+.SH "SEE ALSO"
+addresses(5),
+qmail-header(8),
+qmail-inject(8)
diff --git a/mailsubj.sh b/mailsubj.sh
new file mode 100644 (file)
index 0000000..a93d3f4
--- /dev/null
@@ -0,0 +1,7 @@
+subject="$1"
+shift
+( echo Subject: "$subject"
+  echo To: ${1+"$@"}
+  echo ''
+  cat
+) | QMAIL/bin/qmail-inject
diff --git a/make-compile.sh b/make-compile.sh
new file mode 100644 (file)
index 0000000..a1eb501
--- /dev/null
@@ -0,0 +1 @@
+echo exec "$CC" -c '${1+"$@"}'
diff --git a/make-load.sh b/make-load.sh
new file mode 100644 (file)
index 0000000..de07d2e
--- /dev/null
@@ -0,0 +1,2 @@
+echo 'main="$1"; shift'
+echo exec "$LD" '-o "$main" "$main".o ${1+"$@"}'
diff --git a/make-makelib.sh b/make-makelib.sh
new file mode 100644 (file)
index 0000000..d6b7c8c
--- /dev/null
@@ -0,0 +1,16 @@
+echo 'main="$1"; shift'
+echo 'rm -f "$main"'
+echo 'ar cr "$main" ${1+"$@"}'
+
+case "$1" in
+sunos-5.*) ;;
+unix_sv*) ;;
+irix64-*) ;;
+irix-*) ;;
+dgux-*) ;;
+hp-ux-*) ;;
+sco*) ;;
+*)
+  echo 'ranlib "$main"'
+  ;;
+esac
diff --git a/mbox.5 b/mbox.5
new file mode 100644 (file)
index 0000000..de1f837
--- /dev/null
+++ b/mbox.5
@@ -0,0 +1,235 @@
+.TH mbox 5
+.SH "NAME"
+mbox \- file containing mail messages
+.SH "INTRODUCTION"
+The most common format for storage of mail messages is
+.I mbox
+format.
+An
+.I mbox
+is a single file containing zero or more mail messages.
+.SH "MESSAGE FORMAT"
+A message encoded in
+.I mbox
+format begins with a
+.B From_
+line, continues with a series of
+.B \fRnon-\fBFrom_
+lines,
+and ends with a blank line.
+A
+.B From_
+line means any line that begins with the characters
+F, r, o, m, space:
+
+.EX
+     From god@heaven.af.mil Sat Jan  3 01:05:34 1996
+.br
+     Return-Path: <god@heaven.af.mil>
+.br
+     Delivered-To: djb@silverton.berkeley.edu
+.br
+     Date: 3 Jan 1996 01:05:34 -0000
+.br
+     From: God <god@heaven.af.mil>
+.br
+     To: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+
+.br
+     How's that mail system project coming along?
+.br
+
+.EE
+
+The final line is a completely blank line (no spaces or tabs).
+Notice that blank lines may also appear elsewhere in the message.
+
+The
+.B From_
+line always looks like
+.B From
+.I envsender
+.I date
+.IR moreinfo .
+.I envsender
+is one word, without spaces or tabs;
+it is usually the envelope sender of the message.
+.I date
+is the delivery date of the message.
+It always contains exactly 24 characters in
+.B asctime
+format.
+.I moreinfo
+is optional; it may contain arbitrary information.
+
+Between the
+.B From_
+line and the blank line is a message in RFC 822 format,
+as described in
+.BR qmail-header(5) ,
+subject to
+.B >From quoting
+as described below.
+.SH "HOW A MESSAGE IS DELIVERED"
+Here is how a program appends a message to an
+.I mbox
+file.
+
+It first creates a
+.B From_
+line given the message's envelope sender and the current date.
+If the envelope sender is empty (i.e., if this is a bounce message),
+the program uses
+.B MAILER-DAEMON
+instead.
+If the envelope sender contains spaces, tabs, or newlines,
+the program replaces them with hyphens.
+
+The program then copies the message, applying
+.B >From quoting
+to each line.
+.B >From quoting
+ensures that the resulting lines are not
+.B From_
+lines:
+the program prepends a
+.B >
+to any
+.B From_
+line,
+.B >From_
+line,
+.B >>From_
+line,
+.B >>>From_
+line,
+etc.
+
+Finally the program appends a blank line to the message.
+If the last line of the message was a partial line,
+it writes two newlines;
+otherwise it writes one.
+.SH "HOW A MESSAGE IS READ"
+A reader scans through an
+.I mbox
+file looking for
+.B From_
+lines.
+Any
+.B From_
+line marks the beginning of a message.
+The reader should not attempt to take advantage of the fact that every
+.B From_
+line (past the beginning of the file)
+is preceded by a blank line.
+
+Once the reader finds a message,
+it extracts a (possibly corrupted) envelope sender
+and delivery date out of the
+.B From_
+line.
+It then reads until the next
+.B From_
+line or end of file, whichever comes first.
+It strips off the final blank line
+and
+deletes the
+quoting of
+.B >From_
+lines and
+.B >>From_
+lines and so on.
+The result is an RFC 822 message.
+.SH "COMMON MBOX VARIANTS"
+There are many variants of
+.I mbox
+format.
+The variant described above is
+.I mboxrd
+format, popularized by Rahul Dhesi in June 1995.
+
+The original
+.I mboxo
+format quotes only
+.B From_
+lines, not
+.B >From_
+lines.
+As a result it is impossible to tell whether
+
+.EX
+     From: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+     To: god@heaven.af.mil
+.br
+
+.br
+     >From now through August I'll be doing beta testing.
+.br
+     Thanks for your interest.
+.EE
+
+was quoted in the original message.
+An
+.I mboxrd
+reader will always strip off the quoting.
+
+.I mboxcl
+format is like
+.I mboxo
+format, but includes a Content-Length field with the 
+number of bytes in the message.
+.I mboxcl2
+format is like
+.I mboxcl
+but has no
+.B >From
+quoting.
+These formats are used by SVR4 mailers.
+.I mboxcl2
+cannot be read safely by
+.I mboxrd
+readers.
+.SH "UNSPECIFIED DETAILS"
+There are many locking mechanisms for
+.I mbox
+files.
+.B qmail-local
+always uses
+.B flock
+on systems that have it, otherwise
+.BR lockf .
+
+The delivery date in a
+.B From_
+line does not specify a time zone.
+.B qmail-local
+always creates the delivery date in GMT
+so that 
+.I mbox
+files can be safely transported from one time zone to another.
+
+If the mtime on a nonempty 
+.I mbox
+file is greater than the atime,
+the file has new mail.
+If the mtime is smaller than the atime,
+the new mail has been read.
+If the atime equals the mtime,
+there is no way to tell whether the file has new mail,
+since
+.B qmail-local
+takes much less than a second to run.
+One solution is for a mail reader to artificially set the
+atime to the mtime plus 1.
+Then the file has new mail if and only if the atime is
+less than or equal to the mtime.
+
+Some mail readers place
+.B Status
+fields in each message to indicate which messages have been read.
+.SH "SEE ALSO"
+maildir(5),
+qmail-header(5),
+qmail-local(8)
diff --git a/myctime.c b/myctime.c
new file mode 100644 (file)
index 0000000..5003c39
--- /dev/null
+++ b/myctime.c
@@ -0,0 +1,37 @@
+#include "datetime.h"
+#include "fmt.h"
+#include "myctime.h"
+
+static char *daytab[7] = {
+"Sun","Mon","Tue","Wed","Thu","Fri","Sat"
+};
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+static char result[30];
+
+char *myctime(t)
+datetime_sec t;
+{
+ struct datetime dt;
+ unsigned int len;
+ datetime_tai(&dt,t);
+ len = 0;
+ len += fmt_str(result + len,daytab[dt.wday]);
+ result[len++] = ' ';
+ len += fmt_str(result + len,montab[dt.mon]);
+ result[len++] = ' ';
+ len += fmt_uint0(result + len,dt.mday,2);
+ result[len++] = ' ';
+ len += fmt_uint0(result + len,dt.hour,2);
+ result[len++] = ':';
+ len += fmt_uint0(result + len,dt.min,2);
+ result[len++] = ':';
+ len += fmt_uint0(result + len,dt.sec,2);
+ result[len++] = ' ';
+ len += fmt_uint(result + len,1900 + dt.year);
+ result[len++] = '\n';
+ result[len++] = 0;
+ return result;
+}
diff --git a/myctime.h b/myctime.h
new file mode 100644 (file)
index 0000000..ec1d03c
--- /dev/null
+++ b/myctime.h
@@ -0,0 +1,6 @@
+#ifndef MYCTIME_H
+#define MYCTIME_H
+
+extern char *myctime();
+
+#endif
diff --git a/ndelay.c b/ndelay.c
new file mode 100644 (file)
index 0000000..207e7f2
--- /dev/null
+++ b/ndelay.c
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "ndelay.h"
+
+int ndelay_on(fd)
+int fd;
+{
+  return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) | O_NDELAY);
+}
diff --git a/ndelay.h b/ndelay.h
new file mode 100644 (file)
index 0000000..68c6bce
--- /dev/null
+++ b/ndelay.h
@@ -0,0 +1,7 @@
+#ifndef NDELAY_H
+#define NDELAY_H
+
+extern int ndelay_on();
+extern int ndelay_off();
+
+#endif
diff --git a/ndelay_off.c b/ndelay_off.c
new file mode 100644 (file)
index 0000000..2c89dc3
--- /dev/null
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "ndelay.h"
+
+int ndelay_off(fd)
+int fd;
+{
+  return fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0) & ~O_NDELAY);
+}
diff --git a/newfield.c b/newfield.c
new file mode 100644 (file)
index 0000000..78d6bb8
--- /dev/null
@@ -0,0 +1,68 @@
+#include "fmt.h"
+#include "datetime.h"
+#include "stralloc.h"
+#include "date822fmt.h"
+#include "newfield.h"
+
+/* "Date: 26 Sep 1995 04:46:53 -0000\n" */
+stralloc newfield_date = {0};
+/* "Message-ID: <19950926044653.12345.qmail@silverton.berkeley.edu>\n" */
+stralloc newfield_msgid = {0};
+
+static unsigned int datefmt(s,when)
+char *s;
+datetime_sec when;
+{
+ unsigned int i;
+ unsigned int len;
+ struct datetime dt;
+ datetime_tai(&dt,when);
+ len = 0;
+ i = fmt_str(s,"Date: "); len += i; if (s) s += i;
+ i = date822fmt(s,&dt); len += i; if (s) s += i;
+ return len;
+}
+
+static unsigned int msgidfmt(s,idhost,idhostlen,when)
+char *s;
+char *idhost;
+int idhostlen;
+datetime_sec when;
+{
+ unsigned int i;
+ unsigned int len;
+ struct datetime dt;
+ datetime_tai(&dt,when);
+ len = 0;
+ i = fmt_str(s,"Message-ID: <"); len += i; if (s) s += i;
+ i = fmt_uint(s,dt.year + 1900); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.mon + 1,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.mday,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.hour,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.min,2); len += i; if (s) s += i;
+ i = fmt_uint0(s,dt.sec,2); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_uint(s,getpid()); len += i; if (s) s += i;
+ i = fmt_str(s,".qmail@"); len += i; if (s) s += i;
+ i = fmt_strn(s,idhost,idhostlen); len += i; if (s) s += i;
+ i = fmt_str(s,">\n"); len += i; if (s) s += i;
+ return len;
+}
+
+int newfield_datemake(when)
+datetime_sec when;
+{
+ if (!stralloc_ready(&newfield_date,datefmt(FMT_LEN,when))) return 0;
+ newfield_date.len = datefmt(newfield_date.s,when);
+ return 1;
+}
+
+int newfield_msgidmake(idhost,idhostlen,when)
+char *idhost;
+int idhostlen;
+datetime_sec when;
+{
+ if (!stralloc_ready(&newfield_msgid,msgidfmt(FMT_LEN,idhost,idhostlen,when))) return 0;
+ newfield_msgid.len = msgidfmt(newfield_msgid.s,idhost,idhostlen,when);
+ return 1;
+}
diff --git a/newfield.h b/newfield.h
new file mode 100644 (file)
index 0000000..a51352a
--- /dev/null
@@ -0,0 +1,12 @@
+#ifndef NEWFIELD_H
+#define NEWFIELD_H
+
+#include "stralloc.h"
+
+extern stralloc newfield_date;
+extern int newfield_datemake();
+
+extern stralloc newfield_msgid;
+extern int newfield_msgidmake();
+
+#endif
diff --git a/now.3 b/now.3
new file mode 100644 (file)
index 0000000..3d845b1
--- /dev/null
+++ b/now.3
@@ -0,0 +1,14 @@
+.TH now 3
+.SH NAME
+now \- get current time, in seconds
+.SH SYNTAX
+.B #include <now.h>
+
+datetime_sec \fBnow\fP();
+.SH DESCRIPTION
+.B now
+returns the number of real-time seconds that have elapsed
+since the end of 1969 TAI.
+.SH "SEE ALSO"
+datetime(3),
+time(3)
diff --git a/now.c b/now.c
new file mode 100644 (file)
index 0000000..5ce4d90
--- /dev/null
+++ b/now.c
@@ -0,0 +1,8 @@
+#include <time.h>
+#include "datetime.h"
+#include "now.h"
+
+datetime_sec now()
+{
+  return time((long *) 0);
+}
diff --git a/now.h b/now.h
new file mode 100644 (file)
index 0000000..b8be182
--- /dev/null
+++ b/now.h
@@ -0,0 +1,8 @@
+#ifndef NOW_H
+#define NOW_H
+
+#include "datetime.h"
+
+extern datetime_sec now();
+
+#endif
diff --git a/open.h b/open.h
new file mode 100644 (file)
index 0000000..c903113
--- /dev/null
+++ b/open.h
@@ -0,0 +1,10 @@
+#ifndef OPEN_H
+#define OPEN_H
+
+extern int open_read();
+extern int open_excl();
+extern int open_append();
+extern int open_trunc();
+extern int open_write();
+
+#endif
diff --git a/open_append.c b/open_append.c
new file mode 100644 (file)
index 0000000..93a0862
--- /dev/null
@@ -0,0 +1,6 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "open.h"
+
+int open_append(fn) char *fn;
+{ return open(fn,O_WRONLY | O_NDELAY | O_APPEND | O_CREAT,0600); }
diff --git a/open_excl.c b/open_excl.c
new file mode 100644 (file)
index 0000000..887e7c8
--- /dev/null
@@ -0,0 +1,6 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "open.h"
+
+int open_excl(fn) char *fn;
+{ return open(fn,O_WRONLY | O_EXCL | O_CREAT,0644); }
diff --git a/open_read.c b/open_read.c
new file mode 100644 (file)
index 0000000..f503e48
--- /dev/null
@@ -0,0 +1,6 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "open.h"
+
+int open_read(fn) char *fn;
+{ return open(fn,O_RDONLY | O_NDELAY); }
diff --git a/open_trunc.c b/open_trunc.c
new file mode 100644 (file)
index 0000000..e275085
--- /dev/null
@@ -0,0 +1,6 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "open.h"
+
+int open_trunc(fn) char *fn;
+{ return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); }
diff --git a/open_write.c b/open_write.c
new file mode 100644 (file)
index 0000000..dcdcb95
--- /dev/null
@@ -0,0 +1,6 @@
+#include <sys/types.h>
+#include <fcntl.h>
+#include "open.h"
+
+int open_write(fn) char *fn;
+{ return open(fn,O_WRONLY | O_NDELAY); }
diff --git a/pinq.sh b/pinq.sh
new file mode 100644 (file)
index 0000000..93e747a
--- /dev/null
+++ b/pinq.sh
@@ -0,0 +1 @@
+QMAIL/bin/maildir2mbox && exec pine ${1+"$@"}
diff --git a/predate.c b/predate.c
new file mode 100644 (file)
index 0000000..b011dc5
--- /dev/null
+++ b/predate.c
@@ -0,0 +1,125 @@
+#include <sys/types.h>
+#include <time.h>
+#include "datetime.h"
+#include "fork.h"
+#include "wait.h"
+#include "fd.h"
+#include "fmt.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "readwrite.h"
+#include "exit.h"
+
+static char *montab[12] = {
+"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"
+};
+
+char num[FMT_ULONG];
+char outbuf[1024];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  time_t now;
+  struct tm *tm;
+  struct datetime dt;
+  datetime_sec utc;
+  datetime_sec local;
+  int minutes;
+  int pi[2];
+  substdio ss;
+  int wstat;
+  int pid;
+
+  sig_pipeignore();
+
+  if (!argv[1]) {
+    substdio_putsflush(subfderr,"predate: usage: predate child\n");
+    _exit(100);
+  }
+
+  if (pipe(pi) == -1) {
+    substdio_putsflush(subfderr,"predate: fatal: unable to create pipe\n");
+    _exit(111);
+  }
+
+  switch(pid = fork()) {
+    case -1:
+      substdio_putsflush(subfderr,"predate: fatal: unable to fork\n");
+      _exit(111);
+    case 0:
+      close(pi[1]);
+      if (fd_move(0,pi[0]) == -1) {
+        substdio_putsflush(subfderr,"predate: fatal: unable to set up fds\n");
+        _exit(111);
+      }
+      sig_pipedefault();
+      execvp(argv[1],argv + 1);
+      substdio_putsflush(subfderr,"predate: fatal: unable to exec\n");
+      _exit(111);
+  }
+  close(pi[0]);
+  substdio_fdbuf(&ss,write,pi[1],outbuf,sizeof(outbuf));
+
+  time(&now);
+
+  tm = gmtime(&now);
+  dt.year = tm->tm_year;
+  dt.mon = tm->tm_mon;
+  dt.mday = tm->tm_mday;
+  dt.hour = tm->tm_hour;
+  dt.min = tm->tm_min;
+  dt.sec = tm->tm_sec;
+  utc = datetime_untai(&dt); /* utc == now, if gmtime ignores leap seconds */
+
+  tm = localtime(&now);
+  dt.year = tm->tm_year;
+  dt.mon = tm->tm_mon;
+  dt.mday = tm->tm_mday;
+  dt.hour = tm->tm_hour;
+  dt.min = tm->tm_min;
+  dt.sec = tm->tm_sec;
+  local = datetime_untai(&dt);
+
+  substdio_puts(&ss,"Date: ");
+  substdio_put(&ss,num,fmt_uint(num,dt.mday));
+  substdio_puts(&ss," ");
+  substdio_puts(&ss,montab[dt.mon]);
+  substdio_puts(&ss," ");
+  substdio_put(&ss,num,fmt_uint(num,dt.year + 1900));
+  substdio_puts(&ss," ");
+  substdio_put(&ss,num,fmt_uint0(num,dt.hour,2));
+  substdio_puts(&ss,":");
+  substdio_put(&ss,num,fmt_uint0(num,dt.min,2));
+  substdio_puts(&ss,":");
+  substdio_put(&ss,num,fmt_uint0(num,dt.sec,2));
+
+  if (local < utc) {
+    minutes = (utc - local + 30) / 60;
+    substdio_puts(&ss," -");
+    substdio_put(&ss,num,fmt_uint0(num,minutes / 60,2));
+    substdio_put(&ss,num,fmt_uint0(num,minutes % 60,2));
+  }
+  else {
+    minutes = (local - utc + 30) / 60;
+    substdio_puts(&ss," +");
+    substdio_put(&ss,num,fmt_uint0(num,minutes / 60,2));
+    substdio_put(&ss,num,fmt_uint0(num,minutes % 60,2));
+  }
+
+  substdio_puts(&ss,"\n");
+  substdio_copy(&ss,subfdin);
+  substdio_flush(&ss);
+  close(pi[1]);
+
+  if (wait_pid(&wstat,pid) == -1) {
+    substdio_putsflush(subfderr,"predate: fatal: wait failed\n");
+    _exit(111);
+  }
+  if (wait_crashed(wstat)) {
+    substdio_putsflush(subfderr,"predate: fatal: child crashed\n");
+    _exit(111);
+  }
+  _exit(wait_exitcode(wstat));
+}
diff --git a/preline.1 b/preline.1
new file mode 100644 (file)
index 0000000..69a757c
--- /dev/null
+++ b/preline.1
@@ -0,0 +1,57 @@
+.TH preline 1
+.SH NAME
+preline \- prepend lines to message
+.SH SYNOPSIS
+in
+.BR .qmail\fIext :
+.B | preline \fIcommand
+.SH DESCRIPTION
+.B preline
+feeds each incoming mail message through
+.IR command .
+At the top of each message it inserts
+a UUCP-style
+.B From_
+line, a
+.B Return-Path
+line, and a
+.B Delivered-To
+line.
+
+.B preline
+is useful for
+.B procmail
+and
+ELM's
+.BR filter ,
+which
+do not understand the
+.B qmail-command
+environment variables.
+.SH OPTIONS
+.TP
+.B \-d
+Do not include the
+.B Delivered-To
+line. You should use this option when the
+recipient of the incoming mail message is actually under remote control,
+but was sent here through
+.B control/virtualdomains
+for manual routing.
+.TP
+.B \-f
+Do not include the
+.B From_
+line. You should use this option except for
+.IR command s
+that create
+.I mbox
+files.
+.TP
+.B \-r
+Do not include the
+.B Return-Path
+line.
+.SH "SEE ALSO"
+mbox(5),
+qmail-command(8)
diff --git a/preline.c b/preline.c
new file mode 100644 (file)
index 0000000..8e76485
--- /dev/null
+++ b/preline.c
@@ -0,0 +1,87 @@
+#include "fd.h"
+#include "sgetopt.h"
+#include "readwrite.h"
+#include "subfd.h"
+#include "substdio.h"
+#include "exit.h"
+#include "fork.h"
+#include "wait.h"
+#include "env.h"
+#include "sig.h"
+#include "error.h"
+
+void die(e,s) int e; char *s; { substdio_putsflush(subfderr,s); _exit(e); }
+void die_usage() { die(100,"preline: fatal: incorrect usage\n"); }
+void die_temp() { die(111,"preline: fatal: temporary problem\n"); }
+void die_read() { die(111,"preline: fatal: unable to read message\n"); }
+void die_badcmd() { die(100,"preline: fatal: command not found\n"); }
+
+int flagufline = 1; char *ufline;
+int flagrpline = 1; char *rpline;
+int flagdtline = 1; char *dtline;
+
+substdio ssout;
+char outbuf[SUBSTDIO_OUTSIZE];
+substdio ssin;
+char inbuf[SUBSTDIO_INSIZE];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int opt;
+ int pi[2];
+ int pid;
+ int wstat;
+
+ sig_pipeignore();
+
+ if (!(ufline = env_get("UFLINE"))) die_usage();
+ if (!(rpline = env_get("RPLINE"))) die_usage();
+ if (!(dtline = env_get("DTLINE"))) die_usage();
+
+ while ((opt = getopt(argc,argv,"frdFRD")) != opteof)
+   switch(opt)
+    {
+     case 'f': flagufline = 0; break;
+     case 'r': flagrpline = 0; break;
+     case 'd': flagdtline = 0; break;
+     case 'F': flagufline = 1; break;
+     case 'R': flagrpline = 1; break;
+     case 'D': flagdtline = 1; break;
+     default:
+       _exit(100);
+    }
+ argc -= optind;
+ argv += optind;
+ if (!*argv) die_usage();
+
+ if (pipe(pi) == -1) die_temp();
+
+ switch(pid = fork())
+  {
+   case -1:
+     die_temp();
+   case 0:
+     close(pi[1]);
+     if (fd_move(0,pi[0])) die_temp();
+     sig_pipedefault();
+     execvp(*argv,argv);
+     if (error_temp(errno)) die_temp();
+     die_badcmd();
+  }
+ close(pi[0]);
+
+ substdio_fdbuf(&ssout,write,pi[1],outbuf,sizeof(outbuf));
+ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+ if (flagufline) substdio_bputs(&ssout,ufline);
+ if (flagrpline) substdio_bputs(&ssout,rpline);
+ if (flagdtline) substdio_bputs(&ssout,dtline);
+ if (substdio_copy(&ssout,&ssin) == -2) die_read();
+ substdio_flush(&ssout);
+ close(pi[1]);
+
+ if (wait_pid(&wstat,pid) == -1) die_temp();
+ if (wait_crashed(wstat)) die_temp();
+ _exit(wait_exitcode(wstat));
+}
diff --git a/prioq.c b/prioq.c
new file mode 100644 (file)
index 0000000..051aa45
--- /dev/null
+++ b/prioq.c
@@ -0,0 +1,58 @@
+#include "alloc.h"
+#include "gen_allocdefs.h"
+#include "prioq.h"
+
+GEN_ALLOC_readyplus(prioq,struct prioq_elt,p,len,a,i,n,x,100,prioq_readyplus)
+
+int prioq_insert(pq,pe)
+prioq *pq;
+struct prioq_elt *pe;
+{
+ int i;
+ int j;
+ if (!prioq_readyplus(pq,1)) return 0;
+ j = pq->len++;
+ while (j)
+  {
+   i = (j - 1)/2;
+   if (pq->p[i].dt <= pe->dt) break;
+   pq->p[j] = pq->p[i];
+   j = i;
+  }
+ pq->p[j] = *pe;
+ return 1;
+}
+
+int prioq_min(pq,pe)
+prioq *pq;
+struct prioq_elt *pe;
+{
+ if (!pq->p) return 0;
+ if (!pq->len) return 0;
+ *pe = pq->p[0];
+ return 1;
+}
+
+void prioq_delmin(pq)
+prioq *pq;
+{
+ int i;
+ int j;
+ int n;
+ if (!pq->p) return;
+ n = pq->len;
+ if (!n) return;
+ i = 0;
+ --n;
+ for (;;)
+  {
+   j = i + i + 2;
+   if (j > n) break;
+   if (pq->p[j - 1].dt <= pq->p[j].dt) --j;
+   if (pq->p[n].dt <= pq->p[j].dt) break;
+   pq->p[i] = pq->p[j];
+   i = j;
+  }
+ pq->p[i] = pq->p[n];
+ pq->len = n;
+}
diff --git a/prioq.h b/prioq.h
new file mode 100644 (file)
index 0000000..32a0d7c
--- /dev/null
+++ b/prioq.h
@@ -0,0 +1,15 @@
+#ifndef PRIOQ_H
+#define PRIOQ_H
+
+#include "datetime.h"
+#include "gen_alloc.h"
+
+struct prioq_elt { datetime_sec dt; unsigned long id; } ;
+
+GEN_ALLOC_typedef(prioq,struct prioq_elt,p,len,a)
+
+extern int prioq_insert();
+extern int prioq_min();
+extern void prioq_delmin();
+
+#endif
diff --git a/prot.c b/prot.c
new file mode 100644 (file)
index 0000000..a38e0f9
--- /dev/null
+++ b/prot.c
@@ -0,0 +1,21 @@
+#include "hasshsgr.h"
+#include "prot.h"
+
+/* XXX: there are more portability problems here waiting to leap out at me */
+
+int prot_gid(gid) int gid;
+{
+#ifdef HASSHORTSETGROUPS
+  short x[2];
+  x[0] = gid; x[1] = 73; /* catch errors */
+  if (setgroups(1,x) == -1) return -1;
+#else
+  if (setgroups(1,&gid) == -1) return -1;
+#endif
+  return setgid(gid); /* _should_ be redundant, but on some systems it isn't */
+}
+
+int prot_uid(uid) int uid;
+{
+  return setuid(uid);
+}
diff --git a/prot.h b/prot.h
new file mode 100644 (file)
index 0000000..8c5d81a
--- /dev/null
+++ b/prot.h
@@ -0,0 +1,7 @@
+#ifndef PROT_H
+#define PROT_H
+
+extern int prot_gid();
+extern int prot_uid();
+
+#endif
diff --git a/qail.sh b/qail.sh
new file mode 100644 (file)
index 0000000..7e31c33
--- /dev/null
+++ b/qail.sh
@@ -0,0 +1 @@
+QMAIL/bin/maildir2mbox && exec Mail ${1+"$@"}
diff --git a/qbiff.1 b/qbiff.1
new file mode 100644 (file)
index 0000000..14db027
--- /dev/null
+++ b/qbiff.1
@@ -0,0 +1,31 @@
+.TH qbiff 1
+.SH NAME
+qbiff \- announce new mail the moment it arrives
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |qbiff
+.SH DESCRIPTION
+.B qbiff
+writes a message to your screen
+whenever a new mail message is delivered,
+if you ran
+.B biff y
+after logging in.
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR qbiff ,
+make sure to also add a line specifying delivery to your normal mailbox.
+For example:
+
+.EX
+     /home/joe/Mailbox
+.br
+     |qbiff
+.EE
+.SH "SEE ALSO"
+biff(1),
+dot-qmail(5)
diff --git a/qbiff.c b/qbiff.c
new file mode 100644 (file)
index 0000000..4e9f1d8
--- /dev/null
+++ b/qbiff.c
@@ -0,0 +1,113 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <utmp.h>
+#ifndef UTMP_FILE
+#ifdef _PATH_UTMP
+#define UTMP_FILE _PATH_UTMP
+#else
+#define UTMP_FILE "/etc/utmp"
+#endif
+#endif
+#include "readwrite.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "open.h"
+#include "byte.h"
+#include "str.h"
+#include "headerbody.h"
+#include "hfield.h"
+#include "env.h"
+#include "exit.h"
+
+substdio ssutmp;
+char bufutmp[sizeof(struct utmp) * 16];
+int fdutmp;
+substdio sstty;
+char buftty[1024];
+int fdtty;
+
+struct utmp ut;
+char line[sizeof(ut.ut_line) + 1];
+stralloc woof = {0};
+stralloc tofrom = {0};
+stralloc text = {0};
+
+void doit(s,n) char *s; int n;
+{
+ if (!stralloc_catb(&text,s,n)) _exit(0);
+ if (text.len > 78) text.len = 78;
+}
+void dobody(h) stralloc *h; { doit(h->s,h->len); }
+void doheader(h) stralloc *h;
+{
+ int i;
+ if (hfield_known(h->s,h->len) == H_SUBJECT)
+  {
+   i = hfield_skipname(h->s,h->len);
+   doit(h->s + i,h->len - i);
+  }
+}
+void finishheader() { ; }
+
+void main()
+{
+ char *user;
+ char *sender;
+ char *userext;
+ struct stat st;
+ int i;
+
+ if (chdir("/dev") == -1) _exit(0);
+
+ if (!(user = env_get("USER"))) _exit(0);
+ if (!(sender = env_get("SENDER"))) _exit(0);
+ if (!(userext = env_get("LOCAL"))) _exit(0);
+ if (str_len(user) > sizeof(ut.ut_name)) _exit(0);
+
+ if (!stralloc_copys(&tofrom,"*** TO <")) _exit(0);
+ if (!stralloc_cats(&tofrom,userext)) _exit(0);
+ if (!stralloc_cats(&tofrom,"> FROM <")) _exit(0);
+ if (!stralloc_cats(&tofrom,sender)) _exit(0);
+ if (!stralloc_cats(&tofrom,">")) _exit(0);
+
+ for (i = 0;i < tofrom.len;++i)
+   if ((tofrom.s[i] < 32) || (tofrom.s[i] > 126))
+     tofrom.s[i] = '_';
+
+ if (!stralloc_copys(&text,"    ")) _exit(0);
+ if (headerbody(subfdin,doheader,finishheader,dobody) == -1) _exit(0);
+
+ for (i = 0;i < text.len;++i)
+   if ((text.s[i] < 32) || (text.s[i] > 126))
+     text.s[i] = '/';
+
+ if (!stralloc_copys(&woof,"\015\n\007")) _exit(0);
+ if (!stralloc_cat(&woof,&tofrom)) _exit(0);
+ if (!stralloc_cats(&woof,"\015\n")) _exit(0);
+ if (!stralloc_cat(&woof,&text)) _exit(0);
+ if (!stralloc_cats(&woof,"\015\n")) _exit(0);
+
+ fdutmp = open_read(UTMP_FILE);
+ if (fdutmp == -1) _exit(0);
+ substdio_fdbuf(&ssutmp,read,fdutmp,bufutmp,sizeof(bufutmp));
+
+ while (substdio_get(&ssutmp,&ut,sizeof(ut)) == sizeof(ut))
+   if (!str_diffn(ut.ut_name,user,sizeof(ut.ut_name)))
+    {
+     byte_copy(line,sizeof(ut.ut_line),ut.ut_line);
+     line[sizeof(ut.ut_line)] = 0;
+     if (line[0] == '/') continue;
+     if (!line[0]) continue;
+     if (line[str_chr(line,'.')]) continue;
+     fdtty = open_append(line);
+     if (fdtty == -1) continue;
+     if (fstat(fdtty,&st) == -1) { close(fdtty); continue; }
+     if (!(st.st_mode & 0100)) { close(fdtty); continue; }
+     if (st.st_uid != getuid()) { close(fdtty); continue; }
+     substdio_fdbuf(&sstty,write,fdtty,buftty,sizeof(buftty));
+     substdio_putflush(&sstty,woof.s,woof.len);
+     close(fdtty);
+    }
+ _exit(0);
+}
diff --git a/qlist.1 b/qlist.1
new file mode 100644 (file)
index 0000000..118b53b
--- /dev/null
+++ b/qlist.1
@@ -0,0 +1,112 @@
+.TH qlist 1
+.SH NAME
+qlist \- handle mailing list subscription requests
+.SH SYNOPSIS
+in
+.BR .qmail-\fIlist-request :
+.br
+.B |qlist
+.I user-list@host
+.I user-list-request@host
+.br
+.B .qmail-\fIlist
+.B .qmail-\fIlist-request
+.B .qtemp-\fIlist
+.br
+.I owner
+[
+.I moreinfo
+]
+.br
+(all on one line)
+.SH DESCRIPTION
+.B qlist
+manages a 
+.B qmail
+mailing list.
+
+When
+.B qlist
+receives a message,
+it looks through the body of the message for commands.
+.B WARNING:
+.B qlist
+looks for a command only at the beginning of a line.
+Exception:
+.B qlist
+also looks at
+.B Subject
+lines.
+
+.B qlist
+supports two commands.
+.B SUBSCRIBE
+adds a new subscription to the mailing list;
+.B UNSUBSCRIBE
+removes a subscription.
+.B qlist
+looks for the subscription address in the
+.BR Reply-To ,
+.BR From ,
+or
+.BR Return-Path
+fields in the message.
+
+.B qlist
+inserts an acknowledgment of each action it took,
+along with general help instructions, into the message.
+It then forwards the message to the subscription address,
+with a copy to
+.IR owner .
+
+.BR qlist 's
+general help instructions identify
+.I user-list@host
+as the address of the mailing list,
+.I user-list-request@host
+as the address of
+.B qlist
+itself, and
+.I owner
+as the address
+of the mailing list owner.
+If
+.I moreinfo
+is supplied,
+it is inserted into the middle of the instructions,
+surrounded by blank lines.
+
+.B qlist
+maintains its address list in
+.BR .qmail-\fIlist ,
+so mail to
+.I user-list
+will be forwarded to each subscription address.
+While
+.B qlist
+is editing
+.BR .qmail-\fIlist ,
+it locks
+.BR .qmail-\fIlist-request .
+It uses
+.B .qtemp-\fIlist
+as a temporary file.
+
+Note that
+.B qlist
+only manipulates lines beginning with an ampersand;
+if you manually add an address without an ampersand,
+it cannot be removed by
+.BR qlist .
+
+.B qlist
+automatically sets the execute bit on
+.BR qmail-\fIlist ,
+so
+.B qmail-local
+will ignore any program or file instructions in
+.BR qmail-\fIlist .
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5),
+qmail-queue(8)
diff --git a/qlist.c b/qlist.c
new file mode 100644 (file)
index 0000000..230476f
--- /dev/null
+++ b/qlist.c
@@ -0,0 +1,333 @@
+#include "sig.h"
+#include "readwrite.h"
+#include "substdio.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "getln.h"
+#include "alloc.h"
+#include "str.h"
+#include "env.h"
+#include "hfield.h"
+#include "case.h"
+#include "token822.h"
+#include "error.h"
+#include "gen_alloc.h"
+#include "gen_allocdefs.h"
+#include "headerbody.h"
+#include "exit.h"
+#include "open.h"
+#include "lock.h"
+#include "qmail.h"
+
+#define ADDRLIMIT 100
+
+void die() { _exit(100); }
+void die_temp() { _exit(111); }
+void die_nomem() {
+ substdio_putsflush(subfderr,"qlist: fatal: out of memory\n"); die_temp(); }
+void die_fork() {
+ substdio_putsflush(subfderr,"qlist: fatal: unable to fork\n"); die_temp(); }
+void die_nolock() {
+ substdio_putsflush(subfderr,"qlist: fatal: unable to open lock file\n"); die_temp(); }
+void die_boing() {
+ substdio_putsflush(subfderr,"qlist: fatal: I don't reply to bounces\n"); die(); }
+void die_badaddr() {
+ substdio_putsflush(subfderr,"qlist: fatal: sorry, I'm not allowed to use that address\n"); die(); }
+void die_qqperm() {
+ substdio_putsflush(subfderr,"qlist: fatal: permanent qmail-queue error\n"); die(); }
+void die_qqtemp() {
+ substdio_putsflush(subfderr,"qlist: fatal: temporary qmail-queue error\n"); die_temp(); }
+void die_usage() {
+ substdio_putsflush(subfderr,
+ "qlist: usage: qlist user-list@host user-list-request@host .qmail-list .qmail-list-request .qtemp-list owner [moreinfo]\n"); die(); }
+void die_read() {
+ if (errno == error_nomem) die_nomem();
+ substdio_putsflush(subfderr,"qlist: fatal: read error\n"); die_temp(); }
+void doordie(sa,r) stralloc *sa; int r; {
+ if (r == 1) return; if (r == -1) die_nomem();
+ substdio_putsflush(subfderr,"qlist: fatal: unable to parse this: ");
+ substdio_putflush(subfderr,sa->s,sa->len); die(); }
+
+int subjectaction = 0;
+int numcommands;
+
+int fdlock;
+
+struct qmail qqt;
+
+char *target;
+char *listathost;
+char *requestathost;
+char *qmaillist;
+char *qmailrequest;
+char *qtemplist;
+char *owner;
+char *moreinfo;
+
+char *dtline;
+char *returnpath;
+stralloc safrom = {0};
+stralloc sart = {0};
+
+int rwfrom(addr) token822_alloc *addr; { token822_reverse(addr);
+ if (token822_unquote(&safrom,addr) != 1) die_nomem();
+ token822_reverse(addr); return 1; }
+int rwrt(addr) token822_alloc *addr; { token822_reverse(addr);
+ if (token822_unquote(&sart,addr) != 1) die_nomem();
+ token822_reverse(addr); return 1; }
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+static stralloc sauninit = {0}; saa savedh = {0};
+void savedh_append(h) stralloc *h; {
+ if (!saa_readyplus(&savedh,1)) die_nomem(); savedh.sa[savedh.len] = sauninit;
+ if (!stralloc_copy(savedh.sa + savedh.len,h)) die_nomem(); ++savedh.len; }
+void savedh_print() { int i; for (i = 0;i < savedh.len;++i)
+ qmail_put(&qqt,savedh.sa[i].s,savedh.sa[i].len); }
+
+void finishheader()
+{
+ int i;
+
+ if (sart.s)
+  { if (!stralloc_0(&sart)) die_nomem(); target = sart.s; }
+ else if (safrom.s)
+  { if (!stralloc_0(&safrom)) die_nomem(); target = safrom.s; }
+ else
+   target = returnpath;
+
+ for (i = 0;target[i];++i)
+   if (target[i] == '\n')
+     die_badaddr();
+ if (i > ADDRLIMIT) die_badaddr();
+ if (str_equal(target,"")) die_boing();
+ if (str_equal(target,"#@[]")) die_boing();
+
+ if (qmail_open(&qqt) == -1) die_fork();
+
+ qmail_puts(&qqt,dtline);
+ savedh_print();
+
+ qmail_puts(&qqt,"\n***** Text inserted by ");
+ qmail_puts(&qqt,requestathost);
+ qmail_puts(&qqt,"\n\
+*\n\
+* Hi! This is the qlist program. I'm handling subscriptions for the\n\
+* ");
+ qmail_puts(&qqt,listathost);
+ qmail_puts(&qqt," mailing list.\n\
+*\n");
+ if (moreinfo)
+  {
+   qmail_puts(&qqt,"* ");
+   qmail_puts(&qqt,moreinfo);
+   qmail_puts(&qqt,"\n*\n");
+  }
+ qmail_puts(&qqt,"* My human owner is ");
+ qmail_puts(&qqt,owner);
+ qmail_puts(&qqt,".\n\
+*\n\
+* To the recipient: This message was sent to me on your behalf. (Your\n\
+* address was listed in the Reply-To or From field.) For security,\n\
+* I'm forwarding this message to you, along with my notes.\n\
+*\n\
+* Anyway, to subscribe, send me an empty message. To unsubscribe, send me\n\
+* a message with the word UNSUBSCRIBE at the beginning of a line. Remember,\n\
+* my address is ");
+ qmail_puts(&qqt,requestathost);
+ qmail_puts(&qqt,".\n\
+*\n\
+* Now I'll look for requests inside this message...\n\
+*\n\
+*****\n");
+}
+
+substdio subin; char subinbuf[SUBSTDIO_INSIZE];
+substdio subout; char suboutbuf[SUBSTDIO_OUTSIZE];
+stralloc subline = {0};
+void subscribe(flagadd)
+int flagadd;
+{
+ int fdin;
+ int fdout;
+ int match;
+ int flagwasthere;
+
+ ++numcommands;
+
+ if (lock_ex(fdlock) == -1) goto bad;
+ fdin = open_read(qmaillist);
+ if (fdin == -1) goto badlock;
+ fdout = open_trunc(qtemplist);
+ if (fdout == -1) goto badinlock;
+ if (chmod(qtemplist,0700) == -1) goto badoutinlock;
+
+ flagwasthere = 0;
+
+ substdio_fdbuf(&subin,read,fdin,subinbuf,sizeof(subinbuf));
+ substdio_fdbuf(&subout,write,fdout,suboutbuf,sizeof(suboutbuf));
+ for (;;)
+  {
+   if (getln(&subin,&subline,&match,'\n') == -1) goto badoutinlock;
+   if (!match) break; /* goodbye partial lines */
+   if (subline.len == str_len(target) + 2)
+     if (!str_diffn(subline.s + 1,target,subline.len - 2))
+       if (subline.s[0] == '&')
+        {
+         flagwasthere = 1;
+         if (!flagadd)
+           continue;
+        }
+   if (substdio_put(&subout,subline.s,subline.len) == -1) goto badoutinlock;
+  }
+
+ if (flagadd && !flagwasthere)
+  {
+   if (substdio_puts(&subout,"&") == -1) goto badoutinlock;
+   if (substdio_puts(&subout,target) == -1) goto badoutinlock;
+   if (substdio_puts(&subout,"\n") == -1) goto badoutinlock;
+  }
+ if (substdio_flush(&subout) == -1) goto badoutinlock;
+
+ close(fdout);
+ close(fdin);
+ if (rename(qtemplist,qmaillist) == -1) goto badlock;
+ if (chmod(qmaillist,0500) == -1) goto badlock;
+
+ lock_un(fdlock);
+
+ qmail_puts(&qqt,"***** Text inserted by ");
+ qmail_puts(&qqt,requestathost);
+ qmail_puts(&qqt,"\n*\n* ");
+ if (flagadd)
+   if (flagwasthere)
+    {
+     qmail_puts(&qqt,"Acknowledgment: ");
+     qmail_puts(&qqt,target);
+     qmail_puts(&qqt," was already a subscriber.\n");
+    }
+   else
+    {
+     qmail_puts(&qqt,"Acknowledgment: ");
+     qmail_puts(&qqt,target);
+     qmail_puts(&qqt," is now a subscriber.\n");
+    }
+ else
+   if (flagwasthere)
+    {
+     qmail_puts(&qqt,"Acknowledgment: ");
+     qmail_puts(&qqt,target);
+     qmail_puts(&qqt," is no longer a subscriber.\n");
+    }
+   else
+    {
+     qmail_puts(&qqt,"Hmmm, I don't see ");
+     qmail_puts(&qqt,target);
+     qmail_puts(&qqt," on the subscription list.\n* I'll let my owner know.\n");
+    }
+ qmail_puts(&qqt,"*\n*****\n");
+ return;
+
+badoutinlock: close(fdout);
+badinlock: close(fdin);
+badlock: lock_un(fdlock);
+bad:
+ qmail_puts(&qqt,"***** Text inserted by ");
+ qmail_puts(&qqt,requestathost);
+ qmail_puts(&qqt,"\n*\n\
+* Oh no! Trouble making the new list. I'll let my owner know.\n\
+*\n\
+*****\n");
+}
+
+void dobody(h) stralloc *h;
+{
+ qmail_put(&qqt,h->s,h->len);
+ if (case_starts(h->s,"subs")) subscribe(1);
+ if (case_starts(h->s,"unsu")) subscribe(0);
+}
+
+stralloc hfbuf = {0};
+token822_alloc hfin = {0};
+token822_alloc hfrewrite = {0};
+token822_alloc hfaddr = {0};
+
+void doheaderfield(h)
+stralloc *h;
+{
+ char *x;
+ switch(hfield_known(h->s,h->len))
+  {
+   case H_CONTENTLENGTH: /* SVR4 silliness */
+   case H_CONTENTTYPE:
+   case H_CONTENTTRANSFERENCODING: /* A-bombs 4.2.1.5.2 is idiotic */
+     return;
+   case H_FROM:
+     doordie(h,token822_parse(&hfin,h,&hfbuf));
+     doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwfrom));
+     break;
+   case H_REPLYTO:
+     doordie(h,token822_parse(&hfin,h,&hfbuf));
+     doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwrt));
+     break;
+   case H_SUBJECT:
+     x = h->s + hfield_skipname(h->s,h->len);
+     if (!case_diffb(x,4,"subs")) subjectaction = 1;
+     if (!case_diffb(x,4,"unsu")) subjectaction = 2;
+     break;
+  }
+ savedh_append(h);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ sig_pipeignore();
+
+ if (!(listathost = argv[1])) die_usage();
+ if (!(requestathost = argv[2])) die_usage();
+ if (!(qmaillist = argv[3])) die_usage();
+ if (!(qmailrequest = argv[4])) die_usage();
+ if (!(qtemplist = argv[5])) die_usage();
+ if (!(owner = argv[6])) die_usage();
+ moreinfo = argv[7];
+ if (!(returnpath = env_get("NEWSENDER"))) die_usage();
+ if (!(dtline = env_get("DTLINE"))) die_usage();
+
+ fdlock = open_append(qmailrequest);
+ if (fdlock == -1) die_nolock();
+
+ numcommands = 0;
+ if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1) die_read();
+ if (!numcommands)
+  {
+   qmail_puts(&qqt,"***** Text inserted by ");
+   qmail_puts(&qqt,requestathost);
+   qmail_puts(&qqt,"\n*\n* ");
+   if (subjectaction)
+    {
+     qmail_puts(&qqt,"\
+Hmmm, no commands? Let me check the Subject line...\n*\n*****\n");
+     subscribe(subjectaction == 1);
+    }
+   else
+    {
+     qmail_puts(&qqt,"\
+I didn't see any commands. I presume this is a subscription request.\n\
+*\n*****\n");
+     subscribe(1);
+    }
+  }
+
+ qmail_from(&qqt,returnpath);
+ qmail_to(&qqt,owner);
+ qmail_to(&qqt,target);
+
+ switch(qmail_close(&qqt))
+  {
+   case 0: _exit(0);
+   case QMAIL_TOOLONG: die_qqperm();
+   default: die_qqtemp();
+  }
+}
diff --git a/qlist2.sh b/qlist2.sh
new file mode 100644 (file)
index 0000000..9e31a2a
--- /dev/null
+++ b/qlist2.sh
@@ -0,0 +1 @@
+exec QMAIL/bin/qlist "$USERBREAK$1@$2" "$USERBREAK$1-request@$2" .qmail-"$1" .qmail-"$1"-request .qtemp-"$1" "$USERBREAK$1-owner" ${3+"$3"}
diff --git a/qlx.h b/qlx.h
new file mode 100644 (file)
index 0000000..713946d
--- /dev/null
+++ b/qlx.h
@@ -0,0 +1,18 @@
+#ifndef QLX_H
+#define QLX_H
+
+/* 0, 111, 100 are qmail-local success, soft, hard */
+
+#define QLX_USAGE 112
+#define QLX_BUG 101
+#define QLX_ROOT 113
+#define QLX_NFS 115
+#define QLX_NOALIAS 116
+#define QLX_CDB 117
+#define QLX_SYS 118
+#define QLX_NOMEM 119
+#define QLX_EXECSOFT 120
+#define QLX_EXECPW 121
+#define QLX_EXECHARD 126
+
+#endif
diff --git a/qmail-clean.8 b/qmail-clean.8
new file mode 100644 (file)
index 0000000..41795c6
--- /dev/null
@@ -0,0 +1,13 @@
+.TH qmail-clean 8
+.SH NAME
+qmail-clean \- clean up the queue directory
+.SH SYNOPSIS
+.B qmail-clean
+.SH DESCRIPTION
+.B qmail-clean
+reads a cleanup command from descriptor 0,
+performs the cleanup,
+prints the results to descriptor 1,
+and repeats.
+.SH "SEE ALSO"
+qmail-send(8)
diff --git a/qmail-clean.c b/qmail-clean.c
new file mode 100644 (file)
index 0000000..7539007
--- /dev/null
@@ -0,0 +1,98 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "readwrite.h"
+#include "sig.h"
+#include "now.h"
+#include "str.h"
+#include "direntry.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "byte.h"
+#include "scan.h"
+#include "fmt.h"
+#include "error.h"
+#include "exit.h"
+#include "fmtqfn.h"
+#include "auto_qmail.h"
+
+#define OSSIFIED 129600 /* see qmail-send.c */
+
+stralloc line = {0};
+
+void cleanuppid()
+{
+ DIR *dir;
+ direntry *d;
+ struct stat st;
+ datetime_sec time;
+
+ time = now();
+ dir = opendir("pid");
+ if (!dir) return;
+ while (d = readdir(dir))
+  {
+   if (str_equal(d->d_name,".")) continue;
+   if (str_equal(d->d_name,"..")) continue;
+   if (!stralloc_copys(&line,"pid/")) continue;
+   if (!stralloc_cats(&line,d->d_name)) continue;
+   if (!stralloc_0(&line)) continue;
+   if (stat(line.s,&st) == -1) continue;
+   if (time < st.st_atime + OSSIFIED) continue;
+   unlink(line.s);
+  }
+ closedir(dir);
+}
+
+char fnbuf[FMTQFN];
+
+void respond(s) char *s; { if (substdio_putflush(subfdoutsmall,s,1) == -1) _exit(100); }
+
+void main()
+{
+ int i;
+ int match;
+ int cleanuploop;
+ unsigned long id;
+
+ if (chdir(auto_qmail) == -1) _exit(111);
+ if (chdir("queue") == -1) _exit(111);
+
+ sig_pipeignore();
+
+ if (!stralloc_ready(&line,200)) _exit(111);
+
+ cleanuploop = 0;
+
+ for (;;)
+  {
+   if (cleanuploop) --cleanuploop; else { cleanuppid(); cleanuploop = 30; }
+   if (getln(subfdinsmall,&line,&match,'\0') == -1) break;
+   if (!match) break;
+   if (line.len < 7) { respond("x"); continue; }
+   if (line.len > 100) { respond("x"); continue; }
+   if (line.s[line.len - 1]) { respond("x"); continue; } /* impossible */
+   for (i = 5;i < line.len - 1;++i)
+     if ((unsigned char) (line.s[i] - '0') > 9)
+      { respond("x"); continue; }
+   if (!scan_ulong(line.s + 5,&id)) { respond("x"); continue; }
+   if (byte_equal(line.s,5,"foop/"))
+    {
+#define U(prefix,flag) fmtqfn(fnbuf,prefix,id,flag); \
+if (unlink(fnbuf) == -1) if (errno != error_noent) { respond("!"); continue; }
+     U("intd/",0)
+     U("mess/",1)
+     respond("+");
+    }
+   else if (byte_equal(line.s,4,"todo/"))
+    {
+     U("intd/",0)
+     U("todo/",0)
+     respond("+");
+    }
+   else
+     respond("x");
+  }
+ _exit(0);
+}
diff --git a/qmail-command.8 b/qmail-command.8
new file mode 100644 (file)
index 0000000..c22180e
--- /dev/null
@@ -0,0 +1,123 @@
+.TH qmail-command 8
+.SH NAME
+qmail-command \- user-specified mail delivery program
+.SH SYNOPSIS
+in
+.BR .qmail\fIext :
+.B |\fIcommand
+.SH DESCRIPTION
+.B qmail-local
+will, upon your request,
+feed each incoming mail message through a program of your choice.
+
+When a mail message arrives,
+.B qmail-local
+runs
+.B sh -c \fIcommand
+in your home directory.
+It makes the message available on 
+.IR command 's
+standard input.
+
+.B WARNING:
+The mail message does not begin with
+.BR qmail-local 's
+usual
+.B Return-Path
+and
+.B Delivered-To
+lines.
+
+Note that
+.B qmail-local
+uses the same file descriptor for every delivery
+in your
+.B .qmail
+file, so it is not safe for
+.I command
+to fork a child that
+reads the message in the background while the parent exits.
+
+.IR command 's
+exit codes are interpreted as follows:
+0 means that the delivery was successful;
+99 means that the delivery was successful,
+but that
+.B qmail-local
+should ignore all further delivery instructions;
+100 means that the delivery failed permanently (hard error);
+111 means that the delivery failed but should be tried again
+in a little while (soft error).
+Currently 64, 65, 70, 76, 77, 78, and 112 are considered hard errors,
+and all other codes are considered soft errors,
+but
+.I command
+should avoid relying on this.
+
+.B qmail-local
+supplies several useful environment variables to
+.IR command .
+.B SENDER
+is the envelope sender address.
+.B NEWSENDER
+is the forwarding envelope sender address,
+as described in
+.BR dot-qmail(5) .
+.B RECIPIENT
+is the envelope recipient address,
+.IR local@domain .
+.B USER
+is
+.IR user .
+.B HOME
+is your home directory,
+.IR homedir .
+.B HOST
+is the
+.I domain
+part of the recipient address.
+.B LOCAL
+is the
+.I local
+part.
+.B EXT
+is the
+.B .qmail
+extension,
+.IR ext .
+.B EXT2
+is the portion of
+.B EXT
+following the first dash;
+.B EXT3
+is the portion
+following the second dash;
+.B EXT4
+is the portion
+following the third dash.
+.B DTLINE
+and
+.B RPLINE
+are the usual
+.B Delivered-To
+and
+.B Return-Path
+lines,
+including newlines.
+.B UFLINE
+is the UUCP-style
+.B From_
+line that
+.B qmail-local
+adds to
+.IR mbox -format
+files.
+
+.B WARNING:
+These environment variables are not quoted.
+They may contain special characters.
+They are under the control of a possibly malicious remote user.
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5),
+qmail-local(8)
diff --git a/qmail-config.sh b/qmail-config.sh
new file mode 100644 (file)
index 0000000..8450070
--- /dev/null
@@ -0,0 +1,64 @@
+./hostname | tr '[A-Z]' '[a-z]' | (
+  if read host
+  then
+    echo Your hostname is "$host".
+    ./dnsfq "$host" | tr '[A-Z]' '[a-z]' | (
+      if read fqdn
+      then
+        echo Your host\'s fully qualified name in DNS is "$fqdn".
+       echo Putting "$fqdn" into control/me...
+       echo "$fqdn" > QMAIL/control/me
+       chmod 644 QMAIL/control/me
+       ( echo "$fqdn" | sed 's/^\([^\.]*\)\.\([^\.]*\)\./\2\./' | (
+         read ddom
+         echo Putting "$ddom" into control/defaultdomain...
+         echo "$ddom" > QMAIL/control/defaultdomain
+         chmod 644 QMAIL/control/defaultdomain
+       ) )
+       ( echo "$fqdn" | sed 's/^.*\.\([^\.]*\)\.\([^\.]*\)$/\1.\2/' | (
+         read pdom
+         echo Putting "$pdom" into control/plusdomain...
+         echo "$pdom" > QMAIL/control/plusdomain
+         chmod 644 QMAIL/control/plusdomain
+       ) )
+       echo ' '
+       echo Checking local IP addresses:
+       : > QMAIL/control/locals
+       chmod 644 QMAIL/control/locals
+       ( ./dnsip "$fqdn"
+         ./ipmeprint ) | sort -u | \
+       (
+         while read localip
+         do
+           echo "$localip: " | tr -d '\012'
+           ./dnsptr "$localip" 2>/dev/null | (
+             if read local
+             then
+               echo Adding "$local" to control/locals...
+               echo "$local" >> QMAIL/control/locals
+             else
+               echo PTR lookup failed. I assume this address has no DNS name.
+             fi
+           )
+         done
+       )
+       echo ' '
+       echo If there are any other domain names that point to you,
+       echo you will have to add them to QMAIL/control/locals.
+       echo You don\'t have to worry about aliases, i.e., domains with CNAME records.
+       echo ' '
+       echo Copying QMAIL/control/locals to QMAIL/control/rcpthosts...
+       cp QMAIL/control/locals QMAIL/control/rcpthosts
+       chmod 644 QMAIL/control/rcpthosts
+       echo 'Now qmail will refuse to accept SMTP messages except to those hosts.'
+       echo 'Make sure to change rcpthosts if you add hosts to locals or virtualdomains!'
+      else
+        echo Sorry, I couldn\'t find your host\'s canonical name in DNS.
+        echo You will have to set up control/me yourself.
+      fi
+    )
+  else
+    echo Sorry, I couldn\'t find your hostname.
+    echo You will have to set up control/me yourself.
+  fi
+)
diff --git a/qmail-control.9 b/qmail-control.9
new file mode 100644 (file)
index 0000000..7474a33
--- /dev/null
@@ -0,0 +1,74 @@
+.TH qmail-control 5
+.SH "NAME"
+qmail-control \- qmail configuration files
+.SH "INTRODUCTION"
+You can change the behavior of the
+.B qmail
+system by modifying
+.BR qmail 's
+.I control files
+in
+.BR QMAILHOME/control .
+
+.B qmail
+can survive with just one control file,
+.IR me ,
+containing the
+fully-qualified name of the current host.
+This file is used as the default for
+other hostname-related control files.
+
+Comments are allowed
+in
+.IR badmailfrom ,
+.IR locals ,
+.IR percenthack ,
+.IR rcpthosts ,
+.IR smtproutes ,
+and
+.IR virtualdomains .
+Trailing spaces and tabs are allowed in any control file.
+
+The following table lists all control files
+other than
+.IR me .
+See the corresponding man pages for further details.
+
+.RS
+.nf
+.ta 5c 10c
+control        default used by
+
+.I badmailfrom \fR(none)       \fRqmail-smtpd
+.I bouncefrom  \fRMAILER-DAEMON        \fRqmail-send
+.I bouncehost  \fIme   \fRqmail-send
+.I concurrencylocal    \fR10   \fRqmail-send
+.I concurrencyremote   \fR20   \fRqmail-send
+.I defaultdomain       \fIme   \fRqmail-inject
+.I defaulthost \fIme   \fRqmail-inject
+.I doublebouncehost    \fIme   \fRqmail-send
+.I doublebounceto      \fRpostmaster   \fRqmail-send
+.I envnoathost \fIme   \fRqmail-send
+.I helohost    \fIme   \fRqmail-remote
+.I idhost      \fIme   \fRqmail-inject
+.I localiphost \fIme   \fRqmail-smtpd
+.I locals      \fIme   \fRqmail-send
+.I percenthack \fR(none)       \fRqmail-send
+.I plusdomain  \fIme   \fRqmail-inject
+.I queuelifetime       \fR604800       \fRqmail-send
+.I rcpthosts   \fR(none)       \fRqmail-smtpd
+.I recipientmap        \fR(none)       \fRqmail-send
+.I smtpgreeting        \fIme   \fRqmail-smtpd
+.I smtproutes  \fR(none)       \fRqmail-remote
+.I timeoutconnect      \fR60   \fRqmail-remote
+.I timeoutremote       \fR1200 \fRqmail-remote
+.I timeoutsmtpd        \fR1200 \fRqmail-smtpd
+.I virtualdomains      \fR(none)       \fRqmail-send
+.fi
+.RE
+.SH "SEE ALSO"
+qmail-inject(8),
+qmail-remote(8),
+qmail-send(8),
+qmail-showctl(8),
+qmail-smtpd(8)
diff --git a/qmail-getpw.9 b/qmail-getpw.9
new file mode 100644 (file)
index 0000000..86befb2
--- /dev/null
@@ -0,0 +1,109 @@
+.TH qmail-getpw 8
+.SH NAME
+qmail-getpw \- give addresses to users
+.SH SYNOPSIS
+.B qmail-getpw
+.I local
+.SH DESCRIPTION
+In
+.BR qmail ,
+each user controls a vast array of local addresses.
+.B qmail-getpw
+finds the user that controls a particular address,
+.IR local .
+It prints six pieces of information,
+each terminated by NUL:
+.IR user ;
+.IR uid ;
+.IR gid ;
+.IR homedir ;
+.IR dash ;
+and
+.IR ext .
+The user's account name is
+.IR user ;
+the user's uid and gid in decimal are
+.I uid
+and
+.IR gid ;
+the user's home directory is
+.IR homedir ;
+and messages to
+.I local
+will be handled by
+.IR homedir\fB/.qmail\fIdashext .
+
+In case of trouble,
+.B qmail-getpw
+exits nonzero without printing anything.
+
+.B WARNING:
+The operating system's
+.B getpwnam
+function, which is at the heart of
+.BR qmail-getpw ,
+is inherently unreliable.
+.SH "RULES"
+.B qmail-getpw
+considers an account in
+.B /etc/passwd
+to be a user if
+(1) the account has a nonzero uid,
+(2) the account's home directory exists (and is visible to
+.BR qmail-getpw ),
+and
+(3) the account owns its home directory.
+.B qmail-getpw
+ignores account names containing uppercase letters.
+.B qmail-getpw
+also assumes that all account names are shorter than 32 characters.
+
+.B qmail-getpw
+gives each user
+control over the basic
+.I user
+address and
+all addresses of the form
+.IR user\fBBREAK\fIanything .
+When
+.I local
+is
+.IR user ,
+.I dash
+and
+.I ext
+are both empty.
+When
+.I local
+is
+.IR user\fBBREAK\fIanything ,
+.I dash
+is a hyphen and
+.I ext
+is
+.IR anything .
+.I user
+may appear in any combination of uppercase and lowercase letters
+at the front of
+.IR local .
+
+A catch-all user,
+.BR alias ,
+controls all other addresses.
+In this case
+.I ext
+is
+.I local
+and
+.I dash
+is a hyphen.
+
+You can override all of
+.BR qmail-getpw 's
+decisions with the
+.B qmail-users
+mechanism, which is reliable, highly configurable, and much faster than
+.BR qmail-getpw .
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8)
diff --git a/qmail-getpw.c b/qmail-getpw.c
new file mode 100644 (file)
index 0000000..4833aac
--- /dev/null
@@ -0,0 +1,86 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <pwd.h>
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "error.h"
+#include "exit.h"
+#include "byte.h"
+#include "str.h"
+#include "case.h"
+#include "fmt.h"
+#include "auto_usera.h"
+#include "auto_break.h"
+#include "qlx.h"
+
+#define GETPW_USERLEN 32
+
+char *local;
+struct passwd *pw;
+char *dash;
+char *extension;
+
+int userext()
+{
+  char username[GETPW_USERLEN];
+  struct stat st;
+
+  extension = local + str_len(local);
+  for (;;) {
+    if (extension - local < sizeof(username))
+      if (!*extension || (*extension == *auto_break)) {
+       byte_copy(username,extension - local,local);
+       username[extension - local] = 0;
+       case_lowers(username);
+       pw = getpwnam(username);
+       if (pw)
+         if (pw->pw_uid)
+           if (stat(pw->pw_dir,&st) == 0) {
+             if (st.st_uid == pw->pw_uid) {
+               dash = "";
+               if (*extension) { ++extension; dash = "-"; }
+               return 1;
+             }
+           }
+           else
+             if (error_temp(errno)) _exit(QLX_NFS);
+      }
+    if (extension == local) return 0;
+    --extension;
+  }
+}
+
+char num[FMT_ULONG];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  local = argv[1];
+  if (!local) _exit(100);
+
+  if (!userext()) {
+    extension = local;
+    dash = "-";
+    pw = getpwnam(auto_usera);
+  }
+
+  if (!pw) _exit(QLX_NOALIAS);
+
+  substdio_puts(subfdoutsmall,pw->pw_name);
+  substdio_put(subfdoutsmall,"",1);
+  substdio_put(subfdoutsmall,num,fmt_ulong(num,(long) pw->pw_uid));
+  substdio_put(subfdoutsmall,"",1);
+  substdio_put(subfdoutsmall,num,fmt_ulong(num,(long) pw->pw_gid));
+  substdio_put(subfdoutsmall,"",1);
+  substdio_puts(subfdoutsmall,pw->pw_dir);
+  substdio_put(subfdoutsmall,"",1);
+  substdio_puts(subfdoutsmall,dash);
+  substdio_put(subfdoutsmall,"",1);
+  substdio_puts(subfdoutsmall,extension);
+  substdio_put(subfdoutsmall,"",1);
+  substdio_flush(subfdoutsmall);
+
+  _exit(0);
+}
diff --git a/qmail-header.5 b/qmail-header.5
new file mode 100644 (file)
index 0000000..f15c850
--- /dev/null
@@ -0,0 +1,331 @@
+.TH qmail-header 5
+.SH NAME
+qmail-header \- format of a mail message
+.SH OVERVIEW
+At the top of every mail message is a
+highly structured
+.BR header .
+Many programs expect the header to carry certain information,
+as described below.
+The main function of
+.B qmail-inject
+is to make sure that each outgoing message has an appropriate header.
+
+For more detailed information, see RFC 822 and RFC 1123.
+.SH "MESSAGE STRUCTURE"
+A message contains a series of
+.I header fields\fR,
+a blank line,
+and a
+.IR body :
+
+.EX
+     Received: (qmail-queue invoked by uid 666);
+.br
+            30 Jul 1996 11:54:54 -0000
+.br
+     From: djb@silverton.berkeley.edu (D. J. Bernstein)
+.br
+     To: fred@silverton.berkeley.edu
+.br
+     Date: 30 Jul 1996 11:54:54 -0000
+.br
+     Subject: Go, Bears!
+.br
+
+.br
+     I've got money on this one.  How about you?
+.br
+
+.br
+     ---Dan   (this is the third line of the body)
+.EE
+
+Each header field has a
+.IR name ,
+a colon,
+some
+.IR contents ,
+and a newline:
+
+.EX
+     Subject: Go, Bears!
+.EE
+
+The field contents may be folded across several lines.
+Each line past the first must begin with a space or tab:
+
+.EX
+     Received: (qmail-queue invoked by uid 666);
+.br
+            30 Jul 1996 11:54:54 -0000
+.EE
+
+The field name must not contain spaces, tabs, or colons.
+Also, an empty field name is illegal.
+.B qmail-inject
+does not allow field names with unprintable characters.
+
+Case is irrelevant in field names:
+.B subject
+and
+.B SUBJECT
+and
+.B SuBjEcT
+have the same meaning.
+.SH "ADDRESS LISTS"
+Certain fields, such as
+.BR To ,
+contain
+.I address lists\fR.
+
+An address list contains some number of
+.I addresses
+or
+.I address groups\fR,
+separated by commas:
+
+.EX
+     a@b, c@d (Somebody), A Person <e@f>,
+.br
+        random group: g@h, i@j;, k@l
+.EE
+
+An
+.I address group
+has some text, a colon, a list of addresses,
+and a semicolon:
+
+.EX
+        random group: g@h, i@j;
+.EE
+
+An address can appear in several forms.
+The most common form is
+.IR box@host .
+
+Every address must include a host name.
+If
+.B qmail-inject
+sees a lone box name
+it adds the
+.I default host name\fR.
+
+All host names should be fully qualified.
+.B qmail-inject
+appends the
+.I default domain name
+to any name without dots:
+
+.EX
+     djb@silverton  ->  djb@silverton.berkeley.edu
+.EE
+
+It appends the
+.I plus domain name
+to any name
+that ends with a plus sign:
+
+.EX
+     eric@mammoth.cs+  ->  eric@mammoth.cs.berkeley.edu
+.EE
+
+A host name may be a dotted-decimal address:
+
+.EX
+     djb@[128.32.183.163]
+.EE
+
+RFC 822 allows mailbox names inside angle brackets
+to include
+.I source routes\fR,
+but
+.B qmail-inject
+strips all source routes out of addresses.
+.SH "SENDER ADDRESSES"
+.B qmail-inject
+looks for sender address lists in the following fields:
+.BR Sender ,
+.BR From ,
+.BR Reply-To ,
+.BR Return-Path ,
+.BR Return-Receipt-To ,
+.BR Errors-To ,
+.BR Resent-Sender ,
+.BR Resent-From ,
+.BR Resent-Reply-To .
+
+If there is no
+.B From
+field,
+.B qmail-inject
+adds a new
+.B From
+field with the name of the user invoking
+.B qmail-inject.
+
+RFC 822 requires that certain sender fields contain
+only a single address, but
+.B qmail-inject
+does not enforce this restriction.
+.SH "RECIPIENT ADDRESSES"
+.B qmail-inject
+looks for recipient address lists in the following fields:
+.BR To ,
+.BR Cc ,
+.BR Bcc ,
+.BR Apparently-To ,
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc .
+
+Every message must contain at least one
+.B To
+or
+.B Cc
+or
+.BR Bcc .
+.B qmail-inject
+deletes any
+.B Bcc
+field.
+If there is no
+.B To
+or
+.B Cc
+field,
+.B qmail-inject
+adds a line
+
+.EX
+     Cc: recipient list not shown: ;
+.EE
+
+This complies with RFC 822;
+it also works around some strange
+.B sendmail
+behavior, in case the message is passed through
+.B sendmail
+on another machine.
+.SH STAMPS
+Every message must contain a
+.B Date
+field, with the date in a strict format defined by RFC 822.
+If necessary
+.B qmail-inject
+creates a new
+.B Date
+field with the current date (in GMT).
+
+Every message should contain a
+.B Message-Id
+field.
+The field contents are a unique worldwide identifier for this message.
+If necessary
+.B qmail-inject
+creates a new
+.B Message-Id
+field.
+
+Another important field is
+.BR Received .
+Every time the message is sent from one system to another,
+a new
+.B Received
+field is added to the top of the message.
+.B qmail-inject
+does not create any
+.B Received
+fields.
+.SH "RESENT MESSAGES"
+A message is
+.I forwarded
+if it contains any of the following fields:
+.BR Resent-Sender ,
+.BR Resent-From ,
+.BR Resent-Reply-To ,
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc ,
+.BR Resent-Date ,
+.BR Resent-Message-ID .
+
+If a message is forwarded,
+.B qmail-inject
+changes its behavior as follows.
+
+It deletes any
+.B Resent-Bcc
+field (as well as any
+.B Bcc
+field);
+if there are no
+.B Resent-To
+or
+.B Resent-Cc
+fields,
+.B qmail-inject
+adds an appropriate
+.B Resent-Cc
+line.
+It does
+.I not
+add a
+.B Cc
+line,
+even if neither
+.B To
+nor
+.B Cc
+is present.
+
+If there is no
+.B Resent-From
+field,
+.B qmail-inject
+adds a new
+.B Resent-From
+field.
+It does
+.I not
+add a new
+.B From
+field.
+
+.B qmail-inject
+adds
+.B Resent-Date
+if one is not already present;
+same for
+.BR Resent-Message-Id .
+It does
+.I not
+add new
+.B Date
+or
+.B Message-Id
+fields.
+.SH "OTHER FEATURES"
+Addresses are separated by commas, not spaces.
+When
+.B qmail-inject
+sees an illegal space,
+it inserts a comma:
+
+.EX
+     djb fred  ->  djb, fred
+.EE
+
+.B qmail-inject
+removes all
+.B Return-Path
+header fields.
+
+.B qmail-inject
+also removes any
+.B Content-Length
+fields.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-inject(8)
diff --git a/qmail-hier.c b/qmail-hier.c
new file mode 100644 (file)
index 0000000..eff007e
--- /dev/null
@@ -0,0 +1,248 @@
+#include "subfd.h"
+#include "substdio.h"
+#include "auto_split.h"
+#include "auto_uids.h"
+#include "fmt.h"
+
+char strnum[FMT_ULONG];
+
+void uidgid(uid)
+int uid;
+{
+  substdio_put(subfdout,strnum,fmt_ulong(strnum,(unsigned long) uid));
+  substdio_puts(subfdout,":");
+  substdio_put(subfdout,strnum,fmt_ulong(strnum,(unsigned long) auto_gidq));
+  substdio_puts(subfdout,":");
+}
+
+void copy(uid,mode,sub,fn)
+int uid;
+char *mode;
+char *sub;
+char *fn;
+{
+  substdio_puts(subfdout,"c:");
+  uidgid(uid);
+  substdio_puts(subfdout,mode);
+  substdio_puts(subfdout,":");
+  substdio_puts(subfdout,sub);
+  substdio_puts(subfdout,":");
+  substdio_puts(subfdout,fn);
+  substdio_puts(subfdout,":\n");
+}
+
+void dir(uid,mode,fn)
+int uid;
+char *mode;
+char *fn;
+{
+  substdio_puts(subfdout,"d:");
+  uidgid(uid);
+  substdio_puts(subfdout,mode);
+  substdio_puts(subfdout,":");
+  substdio_puts(subfdout,fn);
+  substdio_puts(subfdout,"::\n");
+}
+
+void dirsplit(uid,mode,fn)
+int uid;
+char *mode;
+char *fn;
+{
+  unsigned long i;
+  dir(uid,mode,fn);
+  for (i = 0;i < auto_split;++i) {
+    substdio_puts(subfdout,"d:");
+    uidgid(uid);
+    substdio_puts(subfdout,mode);
+    substdio_puts(subfdout,":");
+    substdio_puts(subfdout,fn);
+    substdio_puts(subfdout,":/");
+    substdio_put(subfdout,strnum,fmt_ulong(strnum,i));
+    substdio_puts(subfdout,":\n");
+  }
+}
+
+void main()
+{
+  dir(auto_uido,"755","");
+  dir(auto_uido,"755","/control");
+  dir(auto_uido,"755","/users");
+  dir(auto_uido,"755","/bin");
+  dir(auto_uido,"755","/man");
+  dir(auto_uido,"755","/man/cat1");
+  dir(auto_uido,"755","/man/cat5");
+  dir(auto_uido,"755","/man/cat7");
+  dir(auto_uido,"755","/man/cat8");
+  dir(auto_uido,"755","/man/man1");
+  dir(auto_uido,"755","/man/man5");
+  dir(auto_uido,"755","/man/man7");
+  dir(auto_uido,"755","/man/man8");
+
+  dir(auto_uida,"755","/alias");
+  dir(auto_uidq,"750","/queue");
+  dir(auto_uidq,"700","/queue/pid");
+  dir(auto_uidq,"700","/queue/intd");
+  dir(auto_uidq,"750","/queue/todo");
+  dir(auto_uidq,"750","/queue/lock");
+  dir(auto_uids,"700","/queue/bounce");
+
+  substdio_puts(subfdout,"z0:");
+  uidgid(auto_uids);
+  substdio_puts(subfdout,"600:/queue/lock/:sendmutex:\n");
+
+  substdio_puts(subfdout,"z1024:");
+  uidgid(auto_uidr);
+  substdio_puts(subfdout,"644:/queue/lock/:tcpto:\n");
+
+  substdio_puts(subfdout,"p:");
+  uidgid(auto_uids);
+  substdio_puts(subfdout,"622:/queue/lock/:trigger:\n");
+
+  dirsplit(auto_uidq,"750","/queue/mess");
+  dirsplit(auto_uids,"700","/queue/info");
+  dirsplit(auto_uids,"700","/queue/local");
+  dirsplit(auto_uids,"700","/queue/remote");
+
+  copy(auto_uidq,"4711","/bin/","qmail-queue");
+  copy(auto_uido,"700","/bin/","qmail-lspawn");
+  copy(auto_uido,"700","/bin/","qmail-start");
+  copy(auto_uido,"711","/bin/","qmail-getpw");
+  copy(auto_uido,"711","/bin/","qmail-local");
+  copy(auto_uido,"711","/bin/","qmail-remote");
+  copy(auto_uido,"711","/bin/","qmail-rspawn");
+  copy(auto_uido,"711","/bin/","qmail-clean");
+  copy(auto_uido,"711","/bin/","qmail-send");
+  copy(auto_uido,"711","/bin/","splogger");
+  copy(auto_uido,"700","/bin/","qmail-newu");
+  copy(auto_uido,"711","/bin/","qmail-pw2u");
+  copy(auto_uido,"755","/bin/","qmail-inject");
+  copy(auto_uido,"755","/bin/","predate");
+  copy(auto_uido,"755","/bin/","datemail");
+  copy(auto_uido,"755","/bin/","mailsubj");
+  copy(auto_uido,"755","/bin/","qmail-showctl");
+  copy(auto_uido,"755","/bin/","qmail-qread");
+  copy(auto_uido,"755","/bin/","qmail-qstat");
+  copy(auto_uido,"755","/bin/","qmail-tcpto");
+  copy(auto_uido,"755","/bin/","qmail-pop3d");
+  copy(auto_uido,"700","/bin/","qmail-popup");
+  copy(auto_uido,"755","/bin/","qmail-qmtpd");
+  copy(auto_uido,"755","/bin/","qmail-smtpd");
+  copy(auto_uido,"755","/bin/","sendmail");
+  copy(auto_uido,"755","/bin/","tcp-env");
+  copy(auto_uido,"755","/bin/","qlist");
+  copy(auto_uido,"755","/bin/","qlist2");
+  copy(auto_uido,"755","/bin/","qreceipt");
+  copy(auto_uido,"755","/bin/","qsmhook");
+  copy(auto_uido,"755","/bin/","qbiff");
+  copy(auto_uido,"755","/bin/","forward");
+  copy(auto_uido,"755","/bin/","preline");
+  copy(auto_uido,"755","/bin/","condredirect");
+  copy(auto_uido,"755","/bin/","maildirmake");
+  copy(auto_uido,"755","/bin/","maildir2mbox");
+  copy(auto_uido,"755","/bin/","maildirwatch");
+  copy(auto_uido,"755","/bin/","qail");
+  copy(auto_uido,"755","/bin/","elq");
+  copy(auto_uido,"755","/bin/","pinq");
+
+  copy(auto_uido,"644","/man/man5/","addresses.5");
+  copy(auto_uido,"644","/man/cat5/","addresses.0");
+  copy(auto_uido,"644","/man/man5/","envelopes.5");
+  copy(auto_uido,"644","/man/cat5/","envelopes.0");
+  copy(auto_uido,"644","/man/man5/","maildir.5");
+  copy(auto_uido,"644","/man/cat5/","maildir.0");
+  copy(auto_uido,"644","/man/man5/","mbox.5");
+  copy(auto_uido,"644","/man/cat5/","mbox.0");
+  copy(auto_uido,"644","/man/man5/","dot-qmail.5");
+  copy(auto_uido,"644","/man/cat5/","dot-qmail.0");
+  copy(auto_uido,"644","/man/man5/","qmail-control.5");
+  copy(auto_uido,"644","/man/cat5/","qmail-control.0");
+  copy(auto_uido,"644","/man/man5/","qmail-header.5");
+  copy(auto_uido,"644","/man/cat5/","qmail-header.0");
+  copy(auto_uido,"644","/man/man5/","qmail-log.5");
+  copy(auto_uido,"644","/man/cat5/","qmail-log.0");
+  copy(auto_uido,"644","/man/man5/","qmail-users.5");
+  copy(auto_uido,"644","/man/cat5/","qmail-users.0");
+  copy(auto_uido,"644","/man/man5/","tcp-environ.5");
+  copy(auto_uido,"644","/man/cat5/","tcp-environ.0");
+
+  copy(auto_uido,"644","/man/man7/","forgeries.7");
+  copy(auto_uido,"644","/man/cat7/","forgeries.0");
+  copy(auto_uido,"644","/man/man7/","qmail-limits.7");
+  copy(auto_uido,"644","/man/cat7/","qmail-limits.0");
+  copy(auto_uido,"644","/man/man7/","qmail-upgrade.7");
+  copy(auto_uido,"644","/man/cat7/","qmail-upgrade.0");
+  copy(auto_uido,"644","/man/man7/","qmail.7");
+  copy(auto_uido,"644","/man/cat7/","qmail.0");
+
+  copy(auto_uido,"644","/man/man1/","forward.1");
+  copy(auto_uido,"644","/man/cat1/","forward.0");
+  copy(auto_uido,"644","/man/man1/","condredirect.1");
+  copy(auto_uido,"644","/man/cat1/","condredirect.0");
+  copy(auto_uido,"644","/man/man1/","maildirmake.1");
+  copy(auto_uido,"644","/man/cat1/","maildirmake.0");
+  copy(auto_uido,"644","/man/man1/","maildir2mbox.1");
+  copy(auto_uido,"644","/man/cat1/","maildir2mbox.0");
+  copy(auto_uido,"644","/man/man1/","maildirwatch.1");
+  copy(auto_uido,"644","/man/cat1/","maildirwatch.0");
+  copy(auto_uido,"644","/man/man1/","mailsubj.1");
+  copy(auto_uido,"644","/man/cat1/","mailsubj.0");
+  copy(auto_uido,"644","/man/man1/","qlist.1");
+  copy(auto_uido,"644","/man/cat1/","qlist.0");
+  copy(auto_uido,"644","/man/man1/","qreceipt.1");
+  copy(auto_uido,"644","/man/cat1/","qreceipt.0");
+  copy(auto_uido,"644","/man/man1/","qbiff.1");
+  copy(auto_uido,"644","/man/cat1/","qbiff.0");
+  copy(auto_uido,"644","/man/man1/","preline.1");
+  copy(auto_uido,"644","/man/cat1/","preline.0");
+  copy(auto_uido,"644","/man/man1/","tcp-env.1");
+  copy(auto_uido,"644","/man/cat1/","tcp-env.0");
+
+  copy(auto_uido,"644","/man/man8/","qmail-local.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-local.0");
+  copy(auto_uido,"644","/man/man8/","qmail-lspawn.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-lspawn.0");
+  copy(auto_uido,"644","/man/man8/","qmail-getpw.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-getpw.0");
+  copy(auto_uido,"644","/man/man8/","qmail-remote.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-remote.0");
+  copy(auto_uido,"644","/man/man8/","qmail-rspawn.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-rspawn.0");
+  copy(auto_uido,"644","/man/man8/","qmail-clean.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-clean.0");
+  copy(auto_uido,"644","/man/man8/","qmail-send.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-send.0");
+  copy(auto_uido,"644","/man/man8/","qmail-start.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-start.0");
+  copy(auto_uido,"644","/man/man8/","splogger.8");
+  copy(auto_uido,"644","/man/cat8/","splogger.0");
+  copy(auto_uido,"644","/man/man8/","qmail-queue.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-queue.0");
+  copy(auto_uido,"644","/man/man8/","qmail-inject.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-inject.0");
+  copy(auto_uido,"644","/man/man8/","qmail-showctl.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-showctl.0");
+  copy(auto_uido,"644","/man/man8/","qmail-newu.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-newu.0");
+  copy(auto_uido,"644","/man/man8/","qmail-pw2u.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-pw2u.0");
+  copy(auto_uido,"644","/man/man8/","qmail-qread.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-qread.0");
+  copy(auto_uido,"644","/man/man8/","qmail-qstat.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-qstat.0");
+  copy(auto_uido,"644","/man/man8/","qmail-tcpto.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-tcpto.0");
+  copy(auto_uido,"644","/man/man8/","qmail-pop3d.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-pop3d.0");
+  copy(auto_uido,"644","/man/man8/","qmail-popup.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-popup.0");
+  copy(auto_uido,"644","/man/man8/","qmail-qmtpd.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-qmtpd.0");
+  copy(auto_uido,"644","/man/man8/","qmail-smtpd.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-smtpd.0");
+  copy(auto_uido,"644","/man/man8/","qmail-command.8");
+  copy(auto_uido,"644","/man/cat8/","qmail-command.0");
+
+  substdio_flush(subfdout);
+  _exit(0);
+}
diff --git a/qmail-inject.8 b/qmail-inject.8
new file mode 100644 (file)
index 0000000..3a05328
--- /dev/null
@@ -0,0 +1,294 @@
+.TH qmail-inject 8
+.SH NAME
+qmail-inject \- preprocess and send a mail message
+.SH SYNOPSIS
+.B qmail-inject
+[
+.B \-nNaAhH
+] [
+.B \-f\fIsender
+] [
+.I recip ...
+]
+.SH DESCRIPTION
+.B qmail-inject
+reads a mail message from its standard input,
+adds appropriate information to the message header,
+and invokes
+.B qmail-queue
+to send the message
+to one or more recipients.
+
+See
+.B qmail-header(5)
+for information on how
+.B qmail-inject
+rewrites header fields.
+
+.B qmail-inject
+normally exits 0.
+It exits 100 if it was invoked improperly
+or if there is a severe syntax error in the message.
+It exits 111 for temporary errors.
+.SH "ENVIRONMENT VARIABLES"
+For the convenience of users who do not run
+.B qmail-inject
+directly,
+.B qmail-inject
+takes many options through environment variables.
+
+The user name in the
+.B From
+header field is set by
+.BR QMAILUSER ,
+.BR MAILUSER ,
+.BR USER ,
+or
+.BR LOGNAME ,
+whichever comes first.
+
+The host name is normally set by the
+.I defaulthost
+control
+but can be overridden with
+.B QMAILHOST
+or
+.BR MAILHOST .
+
+The personal name is
+.BR QMAILNAME ,
+.BR MAILNAME ,
+or
+.BR NAME .
+
+The default envelope sender address is the same as the
+default
+.B From
+address,
+but it can be overridden with
+.B QMAILSUSER
+and
+.BR QMAILSHOST .
+It may also be modified by the
+.B r
+and
+.B m
+letters described below.
+Bounces will be sent to this address.
+
+The
+.B QMAILINJECT
+environment variable
+can contain any of the following letters:
+.TP
+.B c
+Use address-comment style for the
+.B From
+field.
+Normally
+.B qmail-inject
+uses name-address style.
+.TP
+.B s
+Do not look at any incoming
+.B Return-Path
+field.
+Normally, if
+.B Return-Path
+is supplied, it sets the envelope sender address,
+overriding all environment variables.
+.B Return-Path
+is deleted in any case.
+.TP
+.B f
+Delete any incoming
+.B From
+field.
+Normally, if
+.B From
+is supplied, it overrides the usual
+.B From
+field created by
+.BR qmail-inject .
+.TP
+.B i
+Delete any incoming
+.B Message-ID
+field.
+Normally, if
+.B Message-ID
+is supplied, it overrides the usual
+.B Message-ID
+field created by
+.BR qmail-inject .
+.TP
+.B r
+Use a per-recipient VERP.
+.B qmail-inject
+will append each recipient address to the envelope sender
+of the copy going to that recipient.
+.TP
+.B m
+Use a per-message VERP.
+.B qmail-inject
+will append the current date and process ID to the envelope sender.
+.SH OPTIONS
+.TP
+.B \-a
+Send the message to all addresses given as
+.I recip
+arguments;
+do not use header recipient addresses.
+.TP
+.B \-h
+Send the message to all header recipient addresses.
+For non-forwarded messages, this means
+the addresses listed under
+.BR To ,
+.BR Cc ,
+.BR Bcc ,
+.BR Apparently-To .
+For forwarded messages, this means
+the addresses listed under
+.BR Resent-To ,
+.BR Resent-Cc ,
+.BR Resent-Bcc .
+Do not use any
+.I recip
+arguments.
+.TP
+.B \-A
+(Default.)
+Send the message to all addresses given as
+.I recip
+arguments.
+If no
+.I recip
+arguments are supplied,
+send the message to all header recipient addresses.
+.TP
+.B \-H
+Send the message to all header recipient addresses,
+and to all addresses given as
+.I recip
+arguments.
+.TP
+.B \-f\fIsender
+Pass
+.I sender
+to
+.B qmail-queue
+as the envelope sender address.
+This overrides
+.B Return-Path
+and all environment variables.
+.TP
+.B \-N
+(Default.)
+Feed the resulting message to
+.BR qmail-queue .
+.TP
+.B \-n
+Print the message rather than feeding it to
+.BR qmail-queue .
+.SH "CONTROL FILES"
+.TP 5
+.I defaultdomain
+Default domain name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR defaultdomain ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any host name without dots,
+including
+.I defaulthost
+if
+.I defaulthost
+does not have dots.
+(Exception: see
+.IR plusdomain .)
+
+The
+.B QMAILDEFAULTDOMAIN
+environment variable
+overrides
+.IR defaultdomain .
+.TP 5
+.I defaulthost
+Default host name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR defaulthost ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any address without a host name.
+.I defaulthost
+need not be the current host's name.
+For example,
+you may prefer that outgoing mail show
+just your domain name.
+
+The
+.B QMAILDEFAULTHOST
+environment variable overrides
+.IR defaulthost .
+.TP 5
+.I idhost
+Host name for Message-IDs.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR idhost ,
+which is certainly not what you want.
+.I idhost
+need not be the current host's name.
+For example, you may prefer to use fake
+host names in Message-IDs.
+However,
+.I idhost
+must be a fully-qualified name within your domain,
+and each host in your domain should use a different
+.IR idhost .
+
+The
+.B QMAILIDHOST
+environment variable overrides
+.IR idhost .
+.TP 5
+.I plusdomain
+Plus domain name.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR plusdomain ,
+which is probably not what you want.
+.B qmail-inject
+adds this name to any host name that ends with a plus sign,
+including
+.I defaulthost
+if
+.I defaulthost
+ends with a plus sign.
+If a host name does not have dots but ends with a plus sign,
+.B qmail-inject
+uses
+.IR plusdomain ,
+not
+.IR defaultdomain .
+
+The
+.B QMAILPLUSDOMAIN
+environment variable overrides
+.IR plusdomain .
+.SH "SEE ALSO"
+addresses(5),
+qmail-control(5),
+qmail-header(5),
+qmail-queue(8)
diff --git a/qmail-inject.c b/qmail-inject.c
new file mode 100644 (file)
index 0000000..d6083a7
--- /dev/null
@@ -0,0 +1,735 @@
+#include "sig.h"
+#include "substdio.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "sgetopt.h"
+#include "getln.h"
+#include "alloc.h"
+#include "str.h"
+#include "fmt.h"
+#include "hfield.h"
+#include "token822.h"
+#include "control.h"
+#include "env.h"
+#include "gen_alloc.h"
+#include "gen_allocdefs.h"
+#include "error.h"
+#include "qmail.h"
+#include "now.h"
+#include "exit.h"
+#include "quote.h"
+#include "headerbody.h"
+#include "auto_qmail.h"
+#include "newfield.h"
+
+#define LINELEN 80
+
+datetime_sec starttime;
+
+char *qmopts;
+int flagdeletesender = 0;
+int flagdeletefrom = 0;
+int flagdeletemessid = 0;
+int flagnamecomment = 0;
+int flaghackmess = 0;
+int flaghackrecip = 0;
+char *mailhost;
+char *mailuser;
+int mailusertokentype;
+char *mailrhost;
+char *mailruser;
+
+stralloc control_idhost = {0};
+stralloc control_defaultdomain = {0};
+stralloc control_defaulthost = {0};
+stralloc control_plusdomain = {0};
+
+stralloc sender = {0};
+stralloc envsbuf = {0};
+token822_alloc envs = {0};
+int flagrh;
+
+int flagqueue;
+struct qmail qqt;
+
+void put(s,len) char *s; int len;
+{ if (flagqueue) qmail_put(&qqt,s,len); else substdio_put(subfdout,s,len); }
+void puts(s) char *s; { put(s,str_len(s)); }
+
+void perm() { _exit(100); }
+void temp() { _exit(111); }
+void die_nomem() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: out of memory\n"); temp(); }
+void die_invalid(sa) stralloc *sa; {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: invalid header field: ");
+ substdio_putflush(subfderr,sa->s,sa->len); perm(); }
+void die_exec() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: unable to exec qmail-queue\n"); temp(); }
+void die_qqt() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: unable to run qmail-queue\n"); temp(); }
+void die_chdir() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: internal bug\n"); temp(); }
+void die_bug() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: internal bug\n"); temp(); }
+void die_read() {
+ if (errno == error_nomem) die_nomem();
+ substdio_putsflush(subfderr,"qmail-inject: fatal: read error\n"); temp(); }
+void doordie(sa,r) stralloc *sa; int r; {
+ if (r == 1) return; if (r == -1) die_nomem();
+ substdio_putsflush(subfderr,"qmail-inject: fatal: unable to parse this line:\n");
+ substdio_putflush(subfderr,sa->s,sa->len); perm(); }
+
+void die_comm() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue lost communications link\n"); temp(); }
+void die_qq() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue died\n"); temp(); }
+void die_qqwrite() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue unable to write message to disk; disk full?\n"); temp(); }
+void die_qqsig() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue was killed\n"); temp(); }
+void die_qqtimeout() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue timed out\n"); temp(); }
+void die_qqtoolong() {
+ substdio_putsflush(subfderr,"qmail-inject: fatal: qmail-queue unhappy with long addresses\n"); perm(); }
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+
+static stralloc sauninit = {0};
+
+saa savedh = {0};
+saa hrlist = {0};
+saa hrrlist = {0};
+saa reciplist = {0};
+int flagresent;
+
+void exitnicely()
+{
+ if (!flagqueue) substdio_flush(subfdout);
+
+ if (flagqueue)
+  {
+   int i;
+
+   if (!stralloc_0(&sender)) die_nomem();
+   qmail_from(&qqt,sender.s);
+
+   for (i = 0;i < reciplist.len;++i)
+    {
+     if (!stralloc_0(&reciplist.sa[i])) die_nomem();
+     qmail_to(&qqt,reciplist.sa[i].s);
+    }
+   if (flagrh)
+     if (flagresent)
+       for (i = 0;i < hrrlist.len;++i)
+       {
+         if (!stralloc_0(&hrrlist.sa[i])) die_nomem();
+        qmail_to(&qqt,hrrlist.sa[i].s);
+       }
+     else
+       for (i = 0;i < hrlist.len;++i)
+       {
+         if (!stralloc_0(&hrlist.sa[i])) die_nomem();
+        qmail_to(&qqt,hrlist.sa[i].s);
+       }
+
+   switch(qmail_close(&qqt))
+    {
+     case 0: break;
+     case QMAIL_CRASHED: die_qqsig();
+     case QMAIL_USAGE: case QMAIL_BUG: die_bug();
+     case QMAIL_EXECSOFT: die_exec();
+     case QMAIL_NOMEM: die_nomem();
+     case QMAIL_READ: die_comm();
+     case QMAIL_WRITE: die_qqwrite();
+     case QMAIL_TOOLONG: die_qqtoolong();
+     case QMAIL_TIMEOUT: die_qqtimeout();
+     default: die_qq();
+    }
+  }
+
+ _exit(0);
+}
+
+void savedh_append(h)
+stralloc *h;
+{
+ if (!saa_readyplus(&savedh,1)) die_nomem();
+ savedh.sa[savedh.len] = sauninit;
+ if (!stralloc_copy(savedh.sa + savedh.len,h)) die_nomem();
+ ++savedh.len;
+}
+
+void savedh_print()
+{
+ int i;
+
+ for (i = 0;i < savedh.len;++i)
+   put(savedh.sa[i].s,savedh.sa[i].len);
+}
+
+stralloc defaultdomainbuf = {0};
+token822_alloc defaultdomain = {0};
+stralloc defaulthostbuf = {0};
+token822_alloc defaulthost = {0};
+stralloc plusdomainbuf = {0};
+token822_alloc plusdomain = {0};
+
+void rwroute(addr)
+token822_alloc *addr;
+{
+ if (addr->t[addr->len - 1].type == TOKEN822_AT)
+   while (addr->len)
+     if (addr->t[--addr->len].type == TOKEN822_COLON)
+       return;
+}
+
+void rwextraat(addr)
+token822_alloc *addr;
+{
+ int i;
+ if (addr->t[0].type == TOKEN822_AT)
+  {
+   --addr->len;
+   for (i = 0;i < addr->len;++i)
+     addr->t[i] = addr->t[i + 1];
+  }
+}
+
+void rwextradot(addr)
+token822_alloc *addr;
+{
+ int i;
+ if (addr->t[0].type == TOKEN822_DOT)
+  {
+   --addr->len;
+   for (i = 0;i < addr->len;++i)
+     addr->t[i] = addr->t[i + 1];
+  }
+}
+
+void rwnoat(addr)
+token822_alloc *addr;
+{
+ int i;
+ int shift;
+
+ for (i = 0;i < addr->len;++i)
+   if (addr->t[i].type == TOKEN822_AT)
+     return;
+ shift = defaulthost.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+ for (i = addr->len - 1;i >= 0;--i)
+   addr->t[i + shift] = addr->t[i];
+ addr->len += shift;
+ for (i = 0;i < shift;++i)
+   addr->t[i] = defaulthost.t[shift - 1 - i];
+}
+
+void rwnodot(addr)
+token822_alloc *addr;
+{
+ int i;
+ int shift;
+ for (i = 0;i < addr->len;++i)
+  {
+   if (addr->t[i].type == TOKEN822_DOT)
+     return;
+   if (addr->t[i].type == TOKEN822_AT)
+     break;
+  }
+ for (i = 0;i < addr->len;++i)
+  {
+   if (addr->t[i].type == TOKEN822_LITERAL)
+     return;
+   if (addr->t[i].type == TOKEN822_AT)
+     break;
+  }
+ shift = defaultdomain.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+ for (i = addr->len - 1;i >= 0;--i)
+   addr->t[i + shift] = addr->t[i];
+ addr->len += shift;
+ for (i = 0;i < shift;++i)
+   addr->t[i] = defaultdomain.t[shift - 1 - i];
+}
+
+void rwplus(addr)
+token822_alloc *addr;
+{
+ int i;
+ int shift;
+
+ if (addr->t[0].type != TOKEN822_ATOM) return;
+ if (!addr->t[0].slen) return;
+ if (addr->t[0].s[addr->t[0].slen - 1] != '+') return;
+
+ --addr->t[0].slen; /* remove + */
+
+ shift = plusdomain.len;
+ if (!token822_readyplus(addr,shift)) die_nomem();
+ for (i = addr->len - 1;i >= 0;--i)
+   addr->t[i + shift] = addr->t[i];
+ addr->len += shift;
+ for (i = 0;i < shift;++i)
+   addr->t[i] = plusdomain.t[shift - 1 - i];
+}
+
+void rwgeneric(addr)
+token822_alloc *addr;
+{
+ if (!addr->len) return; /* don't rewrite <> */
+ if (addr->len >= 2)
+   if (addr->t[1].type == TOKEN822_AT)
+     if (addr->t[0].type == TOKEN822_LITERAL)
+       if (!addr->t[0].slen) /* don't rewrite <foo@[]> */
+        return;
+ rwroute(addr);
+ if (!addr->len) return; /* <@foo:> -> <> */
+ rwextradot(addr);
+ if (!addr->len) return; /* <.> -> <> */
+ rwextraat(addr);
+ if (!addr->len) return; /* <@> -> <> */
+ rwnoat(addr);
+ rwplus(addr);
+ rwnodot(addr);
+}
+
+int setreturn(addr)
+token822_alloc *addr;
+{
+ if (!sender.s)
+  {
+   token822_reverse(addr);
+   if (token822_unquote(&sender,addr) != 1) die_nomem();
+   if (flaghackrecip)
+     if (!stralloc_cats(&sender,"-@[]")) die_nomem();
+   token822_reverse(addr);
+  }
+ return 1;
+}
+
+int rwreturn(addr)
+token822_alloc *addr;
+{
+ rwgeneric(addr);
+ setreturn(addr);
+ return 1;
+}
+
+int rwsender(addr)
+token822_alloc *addr;
+{
+ rwgeneric(addr);
+ return 1;
+}
+
+void rwrecip(addr,xl)
+token822_alloc *addr;
+saa *xl;
+{
+ rwgeneric(addr);
+ token822_reverse(addr);
+ if (!saa_readyplus(xl,1)) die_nomem();
+ xl->sa[xl->len] = sauninit;
+ if (token822_unquote(&xl->sa[xl->len],addr) != 1) die_nomem();
+ ++xl->len;
+ token822_reverse(addr);
+}
+
+int rwhrr(addr) token822_alloc *addr;
+{ rwrecip(addr,&hrrlist); return 1; }
+int rwhr(addr) token822_alloc *addr;
+{ rwrecip(addr,&hrlist); return 1; }
+
+int htypeseen[H_NUM];
+stralloc hfbuf = {0};
+token822_alloc hfin = {0};
+token822_alloc hfrewrite = {0};
+token822_alloc hfaddr = {0};
+
+void doheaderfield(h)
+stralloc *h;
+{
+ int htype;
+ int flagrewrite;
+ int flagrecip;
+ int flagrr;
+
+ htype = hfield_known(h->s,h->len);
+ if (flagdeletefrom) if (htype == H_FROM) return;
+ if (flagdeletemessid) if (htype == H_MESSAGEID) return;
+ if (flagdeletesender) if (htype == H_RETURNPATH) return;
+
+ if (htype)
+   htypeseen[htype] = 1;
+ else
+   if (!hfield_valid(h->s,h->len))
+     die_invalid(h);
+
+ flagrewrite = 0;
+ flagrecip = 0;
+ flagrr = 0;
+ switch(htype)
+  {
+   case H_R_TO: case H_R_CC: case H_R_BCC:
+     flagrr = 1;
+   case H_TO: case H_CC: case H_BCC: case H_APPARENTLYTO:
+     flagrecip = 1;
+   case H_SENDER: case H_FROM: case H_REPLYTO:
+   case H_RETURNRECEIPTTO: case H_ERRORSTO: case H_RETURNPATH:
+   case H_R_SENDER: case H_R_FROM: case H_R_REPLYTO:
+     flagrewrite = 1;
+     break;
+  }
+
+ if (flagrewrite)
+  {
+   doordie(h,token822_parse(&hfin,h,&hfbuf));
+   doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,(htype == H_RETURNPATH) ? rwreturn : (flagrecip ? (flagrr ? rwhrr : rwhr) : rwsender)));
+   if (token822_unparse(h,&hfrewrite,LINELEN) != 1)
+     die_nomem();
+  }
+
+ if (htype == H_BCC) return;
+ if (htype == H_R_BCC) return;
+ if (htype == H_RETURNPATH) return;
+ if (htype == H_CONTENTLENGTH) return; /* some things are just too stupid */
+ savedh_append(h);
+}
+
+void dobody(h)
+stralloc *h;
+{
+ put(h->s,h->len);
+}
+
+stralloc torecip = {0};
+token822_alloc tr = {0};
+
+void dorecip(s)
+char *s;
+{
+ if (!quote2(&torecip,s)) die_nomem();
+ switch(token822_parse(&tr,&torecip,&hfbuf))
+  {
+   case -1: die_nomem();
+   case 0:
+     substdio_puts(subfderr,"qmail-inject: fatal: unable to parse address: ");
+     substdio_puts(subfderr,s);
+     substdio_putsflush(subfderr,"\n");
+     perm();
+  }
+ token822_reverse(&tr);
+ rwrecip(&tr,&reciplist);
+}
+
+stralloc defaultfrom = {0};
+token822_alloc df = {0};
+
+void defaultfrommake()
+{
+ char *fullname;
+ fullname = env_get("QMAILNAME");
+ if (!fullname) fullname = env_get("MAILNAME");
+ if (!fullname) fullname = env_get("NAME");
+ if (!token822_ready(&df,20)) die_nomem();
+ df.len = 0;
+ df.t[df.len].type = TOKEN822_ATOM;
+ df.t[df.len].s = "From";
+ df.t[df.len].slen = 4;
+ ++df.len;
+ df.t[df.len].type = TOKEN822_COLON;
+ ++df.len;
+ if (fullname && !flagnamecomment)
+  {
+   df.t[df.len].type = TOKEN822_QUOTE;
+   df.t[df.len].s = fullname;
+   df.t[df.len].slen = str_len(fullname);
+   ++df.len;
+   df.t[df.len].type = TOKEN822_LEFT;
+   ++df.len;
+  }
+ df.t[df.len].type = mailusertokentype;
+ df.t[df.len].s = mailuser;
+ df.t[df.len].slen = str_len(mailuser);
+ ++df.len;
+ if (mailhost)
+  {
+   df.t[df.len].type = TOKEN822_AT;
+   ++df.len;
+   df.t[df.len].type = TOKEN822_ATOM;
+   df.t[df.len].s = mailhost;
+   df.t[df.len].slen = str_len(mailhost);
+   ++df.len;
+  }
+ if (fullname && !flagnamecomment)
+  {
+   df.t[df.len].type = TOKEN822_RIGHT;
+   ++df.len;
+  }
+ if (fullname && flagnamecomment)
+  {
+   df.t[df.len].type = TOKEN822_COMMENT;
+   df.t[df.len].s = fullname;
+   df.t[df.len].slen = str_len(fullname);
+   ++df.len;
+  }
+ if (token822_unparse(&defaultfrom,&df,LINELEN) != 1) die_nomem();
+ doordie(&defaultfrom,token822_parse(&df,&defaultfrom,&hfbuf));
+ doordie(&defaultfrom,token822_addrlist(&hfrewrite,&hfaddr,&df,rwsender));
+ if (token822_unparse(&defaultfrom,&hfrewrite,LINELEN) != 1) die_nomem();
+}
+
+stralloc defaultreturnpath = {0};
+token822_alloc drp = {0};
+stralloc hackedruser = {0};
+char strnum[FMT_ULONG];
+
+void dodefaultreturnpath()
+{
+ if (!stralloc_copys(&hackedruser,mailruser)) die_nomem();
+ if (flaghackmess)
+  {
+   if (!stralloc_cats(&hackedruser,"-")) die_nomem();
+   if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) starttime))) die_nomem();
+   if (!stralloc_cats(&hackedruser,".")) die_nomem();
+   if (!stralloc_catb(&hackedruser,strnum,fmt_ulong(strnum,(unsigned long) getpid()))) die_nomem();
+  }
+ if (flaghackrecip)
+   if (!stralloc_cats(&hackedruser,"-")) die_nomem();
+ if (!token822_ready(&drp,10)) die_nomem();
+ drp.len = 0;
+ drp.t[drp.len].type = TOKEN822_ATOM;
+ drp.t[drp.len].s = "Return-Path";
+ drp.t[drp.len].slen = 11;
+ ++drp.len;
+ drp.t[drp.len].type = TOKEN822_COLON;
+ ++drp.len;
+ drp.t[drp.len].type = TOKEN822_QUOTE;
+ drp.t[drp.len].s = hackedruser.s;
+ drp.t[drp.len].slen = hackedruser.len;
+ ++drp.len;
+ if (mailrhost)
+  {
+   drp.t[drp.len].type = TOKEN822_AT;
+   ++drp.len;
+   drp.t[drp.len].type = TOKEN822_ATOM;
+   drp.t[drp.len].s = mailrhost;
+   drp.t[drp.len].slen = str_len(mailrhost);
+   ++drp.len;
+  }
+ if (token822_unparse(&defaultreturnpath,&drp,LINELEN) != 1) die_nomem();
+ doordie(&defaultreturnpath,token822_parse(&drp,&defaultreturnpath,&hfbuf));
+ doordie(&defaultreturnpath
+   ,token822_addrlist(&hfrewrite,&hfaddr,&drp,rwreturn));
+ if (token822_unparse(&defaultreturnpath,&hfrewrite,LINELEN) != 1) die_nomem();
+}
+
+void finishheader()
+{
+ flagresent =
+   htypeseen[H_R_SENDER] || htypeseen[H_R_FROM] || htypeseen[H_R_REPLYTO]
+   || htypeseen[H_R_TO] || htypeseen[H_R_CC] || htypeseen[H_R_BCC]
+   || htypeseen[H_R_DATE] || htypeseen[H_R_MESSAGEID];
+
+ if (!sender.s)
+   dodefaultreturnpath();
+
+ if (!flagqueue)
+  {
+   static stralloc sa = {0};
+   static stralloc sa2 = {0};
+
+   if (!stralloc_copy(&sa,&sender)) die_nomem();
+   if (!stralloc_0(&sa)) die_nomem();
+   if (!quote2(&sa2,sa.s)) die_nomem();
+
+   puts("Return-Path: <");
+   put(sa2.s,sa2.len);
+   puts(">\n");
+  }
+
+ /* could check at this point whether there are any recipients */
+ if (flagqueue)
+   if (qmail_open(&qqt) == -1) die_qqt();
+
+ if (flagresent)
+  {
+   if (!htypeseen[H_R_DATE])
+    {
+     if (!newfield_datemake(starttime)) die_nomem();
+     puts("Resent-");
+     put(newfield_date.s,newfield_date.len);
+    }
+   if (!htypeseen[H_R_MESSAGEID])
+    {
+     if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem();
+     puts("Resent-");
+     put(newfield_msgid.s,newfield_msgid.len);
+    }
+   if (!htypeseen[H_R_FROM])
+    {
+     defaultfrommake();
+     puts("Resent-");
+     put(defaultfrom.s,defaultfrom.len);
+    }
+   if (!htypeseen[H_R_TO] && !htypeseen[H_R_CC])
+     puts("Resent-Cc: recipient list not shown: ;\n");
+  }
+ else
+  {
+   if (!htypeseen[H_DATE])
+    {
+     if (!newfield_datemake(starttime)) die_nomem();
+     put(newfield_date.s,newfield_date.len);
+    }
+   if (!htypeseen[H_MESSAGEID])
+    {
+     if (!newfield_msgidmake(control_idhost.s,control_idhost.len,starttime)) die_nomem();
+     put(newfield_msgid.s,newfield_msgid.len);
+    }
+   if (!htypeseen[H_FROM])
+    {
+     defaultfrommake();
+     put(defaultfrom.s,defaultfrom.len);
+    }
+   if (!htypeseen[H_TO] && !htypeseen[H_CC])
+     puts("Cc: recipient list not shown: ;\n");
+  }
+
+ savedh_print();
+}
+
+void getcontrols()
+{
+ static stralloc sa = {0};
+ char *x;
+
+ if (control_init() == -1) die_read();
+
+ if (control_rldef(&control_defaultdomain,"control/defaultdomain",1,"defaultdomain") != 1)
+   die_read();
+ x = env_get("QMAILDEFAULTDOMAIN");
+ if (x) if (!stralloc_copys(&control_defaultdomain,x)) die_nomem();
+ if (!stralloc_copys(&sa,".")) die_nomem();
+ if (!stralloc_cat(&sa,&control_defaultdomain)) die_nomem();
+ doordie(&sa,token822_parse(&defaultdomain,&sa,&defaultdomainbuf));
+
+ if (control_rldef(&control_defaulthost,"control/defaulthost",1,"defaulthost") != 1)
+   die_read();
+ x = env_get("QMAILDEFAULTHOST");
+ if (x) if (!stralloc_copys(&control_defaulthost,x)) die_nomem();
+ if (!stralloc_copys(&sa,"@")) die_nomem();
+ if (!stralloc_cat(&sa,&control_defaulthost)) die_nomem();
+ doordie(&sa,token822_parse(&defaulthost,&sa,&defaulthostbuf));
+
+ if (control_rldef(&control_plusdomain,"control/plusdomain",1,"plusdomain") != 1)
+   die_read();
+ x = env_get("QMAILPLUSDOMAIN");
+ if (x) if (!stralloc_copys(&control_plusdomain,x)) die_nomem();
+ if (!stralloc_copys(&sa,".")) die_nomem();
+ if (!stralloc_cat(&sa,&control_plusdomain)) die_nomem();
+ doordie(&sa,token822_parse(&plusdomain,&sa,&plusdomainbuf));
+
+ if (control_rldef(&control_idhost,"control/idhost",1,"idhost") != 1)
+   die_read();
+ x = env_get("QMAILIDHOST");
+ if (x) if (!stralloc_copys(&control_idhost,x)) die_nomem();
+}
+
+#define RECIP_DEFAULT 1
+#define RECIP_ARGS 2
+#define RECIP_HEADER 3
+#define RECIP_AH 4
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int i;
+ int opt;
+ int recipstrategy;
+
+ sig_pipeignore();
+
+ starttime = now();
+
+ qmopts = env_get("QMAILINJECT");
+ if (qmopts)
+   while (*qmopts)
+     switch(*qmopts++)
+      {
+       case 'c': flagnamecomment = 1; break;
+       case 's': flagdeletesender = 1; break;
+       case 'f': flagdeletefrom = 1; break;
+       case 'i': flagdeletemessid = 1; break;
+       case 'r': flaghackrecip = 1; break;
+       case 'm': flaghackmess = 1; break;
+      }
+
+ mailhost = env_get("QMAILHOST");
+ if (!mailhost) mailhost = env_get("MAILHOST");
+ mailrhost = env_get("QMAILSHOST");
+ if (!mailrhost) mailrhost = mailhost;
+
+ mailuser = env_get("QMAILUSER");
+ if (!mailuser) mailuser = env_get("MAILUSER");
+ if (!mailuser) mailuser = env_get("USER");
+ if (!mailuser) mailuser = env_get("LOGNAME");
+ if (!mailuser) mailuser = "anonymous";
+ mailusertokentype = TOKEN822_ATOM;
+ if (quote_need(mailuser,str_len(mailuser))) mailusertokentype = TOKEN822_QUOTE;
+ mailruser = env_get("QMAILSUSER");
+ if (!mailruser) mailruser = mailuser;
+
+ for (i = 0;i < H_NUM;++i) htypeseen[i] = 0;
+
+ recipstrategy = RECIP_DEFAULT;
+ flagqueue = 1;
+
+ if (chdir(auto_qmail) == -1)
+   die_chdir();
+ getcontrols();
+
+ if (!saa_readyplus(&hrlist,1)) die_nomem();
+ if (!saa_readyplus(&hrrlist,1)) die_nomem();
+ if (!saa_readyplus(&reciplist,1)) die_nomem();
+
+ while ((opt = getopt(argc,argv,"aAhHnNf:")) != opteof)
+   switch(opt)
+    {
+     case 'a': recipstrategy = RECIP_ARGS; break;
+     case 'A': recipstrategy = RECIP_DEFAULT; break;
+     case 'h': recipstrategy = RECIP_HEADER; break;
+     case 'H': recipstrategy = RECIP_AH; break;
+     case 'n': flagqueue = 0; break;
+     case 'N': flagqueue = 1; break;
+     case 'f':
+       if (!quote2(&sender,optarg)) die_nomem();
+       doordie(&sender,token822_parse(&envs,&sender,&envsbuf));
+       token822_reverse(&envs);
+       rwgeneric(&envs);
+       token822_reverse(&envs);
+       if (token822_unquote(&sender,&envs) != 1) die_nomem();
+       break;
+     case '?':
+     default:
+       perm();
+    }
+ argc -= optind;
+ argv += optind;
+
+ if (recipstrategy == RECIP_DEFAULT)
+   recipstrategy = (*argv ? RECIP_ARGS : RECIP_HEADER);
+
+ if (recipstrategy != RECIP_HEADER)
+   while (*argv)
+     dorecip(*argv++);
+
+ flagrh = (recipstrategy != RECIP_ARGS);
+
+ if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1)
+   die_read();
+ exitnicely();
+}
diff --git a/qmail-limits.9 b/qmail-limits.9
new file mode 100644 (file)
index 0000000..dca5426
--- /dev/null
@@ -0,0 +1,30 @@
+.TH qmail-limits 7
+.SH "NAME"
+qmail-limits \- artificial limits in the qmail system
+.SH "DESCRIPTION"
+The
+.B qmail
+system is able to handle messages of any size,
+addresses of any size, mailing lists of any size, and so on,
+except as limited by the available memory and disk space.
+
+However, it imposes certain artificial limits:
+.TP 5
+1.
+.B qmail-lspawn
+silently limits the number of simultaneous local deliveries to SPAWN.
+.B qmail-rspawn
+silently limits the number of simultaneous remote deliveries to SPAWN.
+.TP 5
+2.
+.B qmail-queue
+rejects any message with an envelope address longer than 1000 characters.
+.TP 5
+3.
+.B qmail-lspawn
+truncates any overly long error report from a delivery program.
+It appends a note saying that it did so.
+.SH "SEE ALSO"
+qmail-lspawn(8),
+qmail-queue(8),
+qmail-rspawn(8)
diff --git a/qmail-local.8 b/qmail-local.8
new file mode 100644 (file)
index 0000000..847c39c
--- /dev/null
@@ -0,0 +1,99 @@
+.TH qmail-local 8
+.SH NAME
+qmail-local \- deliver or forward a mail message
+.SH SYNOPSIS
+.B qmail-local
+[
+.B \-nN
+]
+.I user
+.I homedir
+.I local
+.I dash
+.I ext
+.I domain
+.I sender
+.I aliasempty
+.SH DESCRIPTION
+.B qmail-local
+reads a mail message
+and delivers it to
+.I user
+by the procedure described in
+.BR dot-qmail(5) .
+
+The message's envelope recipient is
+.IR local@domain .
+.B qmail-local
+records
+.I local@domain
+in a new
+.B Delivered-To
+header field.
+If exactly the same
+.B Delivered-To: \fIlocal@domain
+already appears in the header,
+.B qmail-local
+bounces the message,
+to prevent mail forwarding loops.
+
+The message's envelope sender is
+.IR sender .
+.B qmail-local
+records
+.I sender
+in a new
+.B Return-Path
+header field.
+
+.I homedir
+is the user's home directory.
+It must be an absolute directory name.
+
+.I dash
+and
+.I ext
+identify the
+.B .qmail\fIdashext
+file used by
+.BR qmail-local ;
+see
+.BR dot-qmail(5) .
+Normally
+.I dash
+is either empty or a lone hyphen.
+If it is empty,
+.B qmail-local
+treats a nonexistent
+.B .qmail\fIext
+the same way as an empty
+.BR .qmail\fIext :
+namely, following the delivery instructions in
+.IR aliasempty .
+
+The standard input for
+.B qmail-local
+must be a seekable file,
+so that
+.B qmail-local
+can read it more than once.
+.SH "OPTIONS"
+.TP
+.B \-n
+Instead of reading and delivering the message,
+print a description of the delivery instructions.
+.TP
+.B \-N
+(Default.) Read and deliver the message.
+.SH "EXIT CODES"
+0 if the delivery is completely successful;
+nonzero if any delivery instruction failed.
+Exit code 111
+indicates temporary failure.
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5),
+qmail-command(8),
+qmail-queue(8),
+qmail-send(8),
+qmail-lspawn(8)
diff --git a/qmail-local.c b/qmail-local.c
new file mode 100644 (file)
index 0000000..5c441cf
--- /dev/null
@@ -0,0 +1,672 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "readwrite.h"
+#include "sig.h"
+#include "env.h"
+#include "byte.h"
+#include "exit.h"
+#include "fork.h"
+#include "open.h"
+#include "wait.h"
+#include "lock.h"
+#include "seek.h"
+#include "substdio.h"
+#include "getln.h"
+#include "subfd.h"
+#include "sgetopt.h"
+#include "alloc.h"
+#include "error.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "str.h"
+#include "now.h"
+#include "case.h"
+#include "quote.h"
+#include "qmail.h"
+#include "slurpclose.h"
+#include "myctime.h"
+#include "gfrom.h"
+#include "auto_patrn.h"
+
+void err(s) char *s; { substdio_putsflush(subfderr,s); }
+void soft() { _exit(111); }
+void hard() { _exit(100); }
+
+void temp_childcrashed() { err("Aack, child crashed. (#4.3.0)\n"); soft(); }
+void temp_rewind() { err("Unable to rewind message. (#4.3.0)\n"); soft(); }
+void temp_fork() { err("Unable to fork. (#4.3.0)\n"); soft(); }
+void temp_read() { err("Error while reading message. (#4.3.0)\n"); soft(); }
+void temp_write() { err("Error while writing message. (#4.3.0)\n"); soft(); }
+void temp_child() { err("Temporary error in forwarding message. (#4.3.0)\n"); soft(); }
+void temp_maildirtimeout() { err("Timeout on maildir delivery. (#4.3.0)\n"); soft(); }
+void temp_maildir() { err("Temporary error on maildir delivery. (#4.3.0)\n"); soft(); }
+void temp_nomaildir() { err("Unable to chdir to maildir. (#4.2.1)\n"); soft(); }
+void temp_open(fn) char *fn; { err("Unable to open "); err(fn); err(". (#4.2.1)\n"); soft(); }
+
+void temp_blankline() { err("Uh-oh: first line of .qmail file is blank. (#4.2.1)\n"); soft(); }
+void temp_fofile() { err("Uh-oh: .qmail has file delivery but has x bit set. (#4.7.0)\n"); soft(); }
+void temp_foprog() { err("Uh-oh: .qmail has prog delivery but has x bit set. (#4.7.0)\n"); soft(); }
+void temp_nomem() { err("Out of memory. (#4.3.0)\n"); soft(); }
+void temp_chdir() { err("Unable to switch to home directory. (#4.3.0)\n"); soft(); }
+void temp_homestat() { err("Unable to stat home directory. (#4.3.0)\n"); soft(); }
+void temp_homesticky() { err("Home directory is sticky: user is editing his .qmail file. (#4.2.1)\n"); soft(); }
+void temp_homewritable() { err("Uh-oh: home directory is writable. (#4.7.0)\n"); soft(); }
+void temp_qmwritable() { err("Uh-oh: .qmail file is writable. (#4.7.0)\n"); soft(); }
+void temp_nfsqmail() { err("Temporary error trying to open .qmail file. (#4.3.0)\n"); soft(); }
+void temp_denyqmail() { err("Permission error trying to open .qmail file. (#4.3.0)\n"); soft(); }
+void temp_slowlock() { err("File has been locked for 30 seconds straight. (#4.3.0)\n"); soft(); }
+
+void bounce_childperm() { err("Permanent error in forwarding message. (#5.2.4)\n"); hard(); }
+void bounce_loop() { err("This message is looping: it already has my Delivered-To line. (#5.4.6)\n"); hard(); }
+void bounce_ext() { err("Sorry, no mailbox here by that name. (#5.1.1)\n"); hard(); }
+void usage() { err("qmail-local: usage: qmail-local [ -nN ] user homedir local dash ext domain sender aliasempty\n"); hard(); }
+
+void warn_homesticky() { err("Warning: home directory is sticky.\n"); }
+
+int flagdoit;
+int flag99;
+
+char *user;
+char *homedir;
+char *local;
+char *dash;
+char *ext;
+char *host;
+char *sender;
+char *aliasempty;
+
+stralloc dashext = {0};
+stralloc ufline = {0};
+stralloc rpline = {0};
+stralloc envrecip = {0};
+stralloc dtline = {0};
+stralloc qme = {0};
+stralloc ueo = {0};
+stralloc cmds = {0};
+stralloc messline = {0};
+stralloc foo = {0};
+
+char buf[1024];
+char outbuf[1024];
+
+/* child process */
+
+char fntmptph[80 + FMT_ULONG * 2];
+char fnnewtph[80 + FMT_ULONG * 2];
+void tryunlinktmp() { unlink(fntmptph); }
+void sigalrm() { tryunlinktmp(); _exit(3); }
+
+void maildir_child(dir)
+char *dir;
+{
+ unsigned long pid;
+ unsigned long time;
+ char host[64];
+ char *s;
+ int loop;
+ struct stat st;
+ int fd;
+ substdio ss;
+ substdio ssout;
+
+ sig_alarmcatch(sigalrm);
+ if (chdir(dir) == -1) { if (error_temp(errno)) _exit(1); _exit(2); }
+ pid = getpid();
+ host[0] = 0;
+ gethostname(host,sizeof(host));
+ for (loop = 0;;++loop)
+  {
+   time = now();
+   s = fntmptph;
+   s += fmt_str(s,"tmp/");
+   s += fmt_ulong(s,time); *s++ = '.';
+   s += fmt_ulong(s,pid); *s++ = '.';
+   s += fmt_strn(s,host,sizeof(host)); *s++ = 0;
+   if (stat(fntmptph,&st) == -1) if (errno == error_noent) break;
+   /* really should never get to this point */
+   if (loop == 2) _exit(1);
+   sleep(2);
+  }
+ str_copy(fnnewtph,fntmptph);
+ byte_copy(fnnewtph,3,"new");
+
+ alarm(86400);
+ fd = open_excl(fntmptph);
+ if (fd == -1) _exit(1);
+
+ substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
+ substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
+ if (substdio_put(&ssout,rpline.s,rpline.len) == -1) goto fail;
+ if (substdio_put(&ssout,dtline.s,dtline.len) == -1) goto fail;
+
+ switch(substdio_copy(&ssout,&ss))
+  {
+   case -2: tryunlinktmp(); _exit(4);
+   case -3: goto fail;
+  }
+
+ if (substdio_flush(&ssout) == -1) goto fail;
+ if (fsync(fd) == -1) goto fail;
+ if (close(fd) == -1) goto fail; /* NFS dorks */
+
+ if (link(fntmptph,fnnewtph) == -1) goto fail;
+   /* if it was error_exist, almost certainly successful; i hate NFS */
+ tryunlinktmp(); _exit(0);
+
+ fail: tryunlinktmp(); _exit(1);
+}
+
+/* end child process */
+
+void maildir(fn)
+char *fn;
+{
+ int child;
+ int wstat;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ switch(child = fork())
+  {
+   case -1:
+     temp_fork();
+   case 0:
+     maildir_child(fn);
+     soft();
+  }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+   temp_childcrashed();
+ switch(wait_exitcode(wstat))
+  {
+   case 0: break;
+   case 2: temp_nomaildir();
+   case 3: temp_maildirtimeout();
+   case 4: temp_read();
+   default: temp_maildir();
+  }
+}
+
+void slowlock() { temp_slowlock(); }
+
+void mailfile(fn)
+char *fn;
+{
+ int fd;
+ substdio ss;
+ substdio ssout;
+ int match;
+ seek_pos pos;
+ int flaglocked;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ fd = open_append(fn);
+ if (fd == -1) temp_open(fn);
+
+ sig_alarmcatch(slowlock);
+ alarm(30);
+ flaglocked = (lock_ex(fd) != -1);
+ alarm(0);
+ sig_alarmdefault();
+
+ seek_end(fd);
+ pos = seek_cur(fd);
+
+ substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
+ substdio_fdbuf(&ssout,write,fd,outbuf,sizeof(outbuf));
+ if (substdio_put(&ssout,ufline.s,ufline.len)) goto writeerrs;
+ if (substdio_put(&ssout,rpline.s,rpline.len)) goto writeerrs;
+ if (substdio_put(&ssout,dtline.s,dtline.len)) goto writeerrs;
+ for (;;)
+  {
+   if (getln(&ss,&messline,&match,'\n') != 0) 
+    { if (flaglocked) seek_trunc(fd,pos); close(fd); temp_read(); }
+   if (!match && !messline.len) break;
+   if (gfrom(messline.s,messline.len))
+     if (substdio_bput(&ssout,">",1)) goto writeerrs;
+   if (substdio_bput(&ssout,messline.s,messline.len)) goto writeerrs;
+   if (!match)
+    {
+     if (substdio_bputs(&ssout,"\n")) goto writeerrs;
+     break;
+    }
+  }
+ if (substdio_bputs(&ssout,"\n")) goto writeerrs;
+ if (substdio_flush(&ssout)) goto writeerrs;
+ if (fsync(fd) == -1) goto writeerrs;
+ close(fd);
+ return;
+
+ writeerrs:
+ if (flaglocked) seek_trunc(fd,pos);
+ close(fd);
+ temp_write();
+}
+
+void mailprogram(prog)
+char *prog;
+{
+ int child;
+ char *(args[4]);
+ int wstat;
+
+ if (seek_begin(0) == -1) temp_rewind();
+
+ switch(child = fork())
+  {
+   case -1:
+     temp_fork();
+   case 0:
+     args[0] = "sh"; args[1] = "-c"; args[2] = prog; args[3] = 0;
+     sig_pipedefault();
+     execvp(*args,args);
+     if (errno == error_txtbsy) { err("Text busy. (#4.3.0)\n"); soft(); }
+     if (errno == error_nomem) { err("Out of memory. (#4.3.0)\n"); soft(); }
+     if (errno == error_io) { err("I/O error. (#4.3.0)\n"); soft(); }
+     if (error_temp(errno)) { err("Temporary error. (#4.3.0)\n"); soft(); }
+     err("Unable to execute "); err(*args); err(" (#5.2.4)\n");
+     hard();
+  }
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+   temp_childcrashed();
+ switch(wait_exitcode(wstat))
+  {
+   case 100:
+   case 64: case 65: case 70: case 76: case 77: case 78: case 112: hard();
+   case 0: break;
+   case 99: flag99 = 1; break;
+   default: soft();
+  }
+}
+
+unsigned long mailforward_qp = 0;
+
+void mailforward(recips)
+char **recips;
+{
+ struct qmail qqt;
+ substdio ss;
+ int match;
+
+ if (seek_begin(0) == -1) temp_rewind();
+ substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
+
+ if (qmail_open(&qqt) == -1) temp_fork();
+ mailforward_qp = qmail_qp(&qqt);
+ qmail_put(&qqt,dtline.s,dtline.len);
+ do
+  {
+   if (getln(&ss,&messline,&match,'\n') != 0) { qmail_fail(&qqt); break; }
+   qmail_put(&qqt,messline.s,messline.len);
+  }
+ while (match);
+ qmail_from(&qqt,ueo.s);
+ while (*recips) qmail_to(&qqt,*recips++);
+ switch(qmail_close(&qqt))
+  {
+   case QMAIL_TOOLONG: bounce_childperm();
+   case QMAIL_READ: temp_read();
+   case 0: return;
+   default: temp_child();
+  }
+}
+
+void bouncexf()
+{
+ int match;
+ substdio ss;
+
+ if (seek_begin(0) == -1) temp_rewind();
+ substdio_fdbuf(&ss,read,0,buf,sizeof(buf));
+ for (;;)
+  {
+   if (getln(&ss,&messline,&match,'\n') != 0) temp_read();
+   if (!match) break;
+   if (messline.len <= 1)
+     break;
+   if (messline.len == dtline.len)
+     if (!str_diffn(messline.s,dtline.s,dtline.len))
+       bounce_loop();
+  }
+}
+
+void checkhome()
+{
+ struct stat st;
+
+ if (stat(".",&st) == -1) temp_homestat();
+ if (st.st_mode & auto_patrn) temp_homewritable();
+ if (st.st_mode & 01000)
+   if (flagdoit) temp_homesticky(); else warn_homesticky();
+}
+
+int qmeox(dashowner)
+char *dashowner;
+{
+ struct stat st;
+
+ if (!stralloc_copys(&qme,".qmail")) temp_nomem();
+ if (!stralloc_cat(&qme,&dashext)) temp_nomem();
+ if (!stralloc_cats(&qme,dashowner)) temp_nomem();
+ if (!stralloc_0(&qme)) temp_nomem();
+ if (stat(qme.s,&st) == -1)
+  {
+   if (error_temp(errno)) temp_nfsqmail();
+   return -1;
+  }
+ return 0;
+}
+
+int qmeopen(cutable)
+int *cutable;
+{
+ int fd;
+ struct stat st;
+ int i;
+
+ i = dashext.len;
+ for (;;)
+  {
+   if (!stralloc_copys(&qme,".qmail")) temp_nomem();
+   if (!stralloc_catb(&qme,dashext.s,i)) temp_nomem();
+   if (i < dashext.len) if (!stralloc_cats(&qme,"-default")) temp_nomem();
+   if (!stralloc_0(&qme)) temp_nomem();
+   fd = open_read(qme.s);
+   if (fd == -1)
+    {
+     if (error_temp(errno)) temp_nfsqmail();
+     if (errno == error_perm) temp_denyqmail();
+     if (errno == error_acces) temp_denyqmail();
+    }
+   else
+    {
+     if (fstat(fd,&st) == -1) temp_nfsqmail();
+     if ((st.st_mode & S_IFMT) == S_IFREG)
+      {
+       if (st.st_mode & auto_patrn) temp_qmwritable();
+       *cutable = !!(st.st_mode & 0100);
+       return fd;
+      }
+     close(fd);
+    }
+   if (!i) return -1;
+   do
+     if (dashext.s[--i] == '-') break;
+   while (i);
+  }
+}
+
+unsigned long count_file = 0;
+unsigned long count_forward = 0;
+unsigned long count_program = 0;
+char count_buf[FMT_ULONG];
+
+void count_print()
+{
+ substdio_puts(subfdoutsmall,"did ");
+ substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_file));
+ substdio_puts(subfdoutsmall,"+");
+ substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_forward));
+ substdio_puts(subfdoutsmall,"+");
+ substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,count_program));
+ substdio_puts(subfdoutsmall,"\n");
+ if (mailforward_qp)
+  {
+   substdio_puts(subfdoutsmall,"qp ");
+   substdio_put(subfdoutsmall,count_buf,fmt_ulong(count_buf,mailforward_qp));
+   substdio_puts(subfdoutsmall,"\n");
+  }
+ substdio_flush(subfdoutsmall);
+}
+
+void sayit(type,cmd,len)
+char *type;
+char *cmd;
+int len;
+{
+ substdio_puts(subfdoutsmall,type);
+ substdio_put(subfdoutsmall,cmd,len);
+ substdio_putsflush(subfdoutsmall,"\n");
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int opt;
+ int i;
+ int j;
+ int k;
+ int fd;
+ int numforward;
+ char **recips;
+ datetime_sec starttime;
+ int flagforwardonly;
+ char *extx;
+
+ umask(077);
+ sig_pipeignore();
+
+ if (!env_init()) temp_nomem();
+
+ flagdoit = 1;
+ while ((opt = getopt(argc,argv,"nN")) != opteof)
+   switch(opt)
+    {
+     case 'n': flagdoit = 0; break;
+     case 'N': flagdoit = 1; break;
+     case '?':
+     default:
+       hard();
+    }
+ argc -= optind;
+ argv += optind;
+
+ if (!(user = *argv++)) usage();
+ if (!(homedir = *argv++)) usage();
+ if (!(local = *argv++)) usage();
+ if (!(dash = *argv++)) usage();
+ if (!(ext = *argv++)) usage();
+ if (!(host = *argv++)) usage();
+ if (!(sender = *argv++)) usage();
+ if (!(aliasempty = *argv++)) usage();
+ if (*argv) usage();
+
+ if (homedir[0] != '/') usage();
+ if (chdir(homedir) == -1) temp_chdir();
+ checkhome();
+
+ if (!env_put2("HOST",host)) temp_nomem();
+ if (!env_put2("HOME",homedir)) temp_nomem();
+ if (!env_put2("USER",user)) temp_nomem();
+ if (!env_put2("LOCAL",local)) temp_nomem();
+
+ if (!stralloc_copys(&envrecip,local)) temp_nomem();
+ if (!stralloc_cats(&envrecip,"@")) temp_nomem();
+ if (!stralloc_cats(&envrecip,host)) temp_nomem();
+
+ if (!stralloc_copy(&foo,&envrecip)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put2("RECIPIENT",foo.s)) temp_nomem();
+
+ if (!stralloc_copys(&dtline,"Delivered-To: ")) temp_nomem();
+ if (!stralloc_cat(&dtline,&envrecip)) temp_nomem();
+ for (i = 0;i < dtline.len;++i) if (dtline.s[i] == '\n') dtline.s[i] = '_';
+ if (!stralloc_cats(&dtline,"\n")) temp_nomem();
+
+ if (!stralloc_copy(&foo,&dtline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put2("DTLINE",foo.s)) temp_nomem();
+
+ if (flagdoit)
+   bouncexf();
+
+ if (!env_put2("SENDER",sender)) temp_nomem();
+
+ if (!quote2(&foo,sender)) temp_nomem();
+ if (!stralloc_copys(&rpline,"Return-Path: <")) temp_nomem();
+ if (!stralloc_cat(&rpline,&foo)) temp_nomem();
+ for (i = 0;i < rpline.len;++i) if (rpline.s[i] == '\n') rpline.s[i] = '_';
+ if (!stralloc_cats(&rpline,">\n")) temp_nomem();
+
+ if (!stralloc_copy(&foo,&rpline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put2("RPLINE",foo.s)) temp_nomem();
+
+ if (!stralloc_copys(&ufline,"From ")) temp_nomem();
+ if (*sender)
+  {
+   int len; int i; char ch;
+
+   len = str_len(sender);
+   if (!stralloc_readyplus(&ufline,len)) temp_nomem();
+   for (i = 0;i < len;++i)
+    {
+     ch = sender[i];
+     if ((ch == ' ') || (ch == '\t') || (ch == '\n')) ch = '-';
+     ufline.s[ufline.len + i] = ch;
+    }
+   ufline.len += len;
+  }
+ else
+   if (!stralloc_cats(&ufline,"MAILER-DAEMON")) temp_nomem();
+ if (!stralloc_cats(&ufline," ")) temp_nomem();
+ starttime = now();
+ if (!stralloc_cats(&ufline,myctime(starttime))) temp_nomem();
+
+ if (!stralloc_copy(&foo,&ufline)) temp_nomem();
+ if (!stralloc_0(&foo)) temp_nomem();
+ if (!env_put2("UFLINE",foo.s)) temp_nomem();
+
+ if (!stralloc_copys(&dashext,dash)) temp_nomem();
+ if (!stralloc_cats(&dashext,ext)) temp_nomem();
+ for (i = 0;i < dashext.len;++i)
+   if (dashext.s[i] == '.')
+     dashext.s[i] = ':';
+ case_lowerb(dashext.s,dashext.len);
+
+ extx = ext;
+ if (!env_put2("EXT",extx)) temp_nomem();
+ extx += str_chr(extx,'-'); if (*extx) ++extx;
+ if (!env_put2("EXT2",extx)) temp_nomem();
+ extx += str_chr(extx,'-'); if (*extx) ++extx;
+ if (!env_put2("EXT3",extx)) temp_nomem();
+ extx += str_chr(extx,'-'); if (*extx) ++extx;
+ if (!env_put2("EXT4",extx)) temp_nomem();
+
+ flagforwardonly = 0;
+ fd = qmeopen(&flagforwardonly);
+ if (fd == -1) if (*dash) bounce_ext();
+
+ if (!stralloc_copys(&ueo,sender)) temp_nomem();
+ if (str_diff(sender,""))
+   if (str_diff(sender,"#@[]"))
+     if (qmeox("-owner") == 0)
+      {
+       if (qmeox("-owner-default") == 0)
+       {
+         if (!stralloc_copys(&ueo,local)) temp_nomem();
+         if (!stralloc_cats(&ueo,"-owner-@")) temp_nomem();
+         if (!stralloc_cats(&ueo,host)) temp_nomem();
+         if (!stralloc_cats(&ueo,"-@[]")) temp_nomem();
+       }
+       else
+       {
+         if (!stralloc_copys(&ueo,local)) temp_nomem();
+         if (!stralloc_cats(&ueo,"-owner@")) temp_nomem();
+         if (!stralloc_cats(&ueo,host)) temp_nomem();
+       }
+      }
+ if (!stralloc_0(&ueo)) temp_nomem();
+ if (!env_put2("NEWSENDER",ueo.s)) temp_nomem();
+
+ if (!stralloc_ready(&cmds,0)) temp_nomem();
+ cmds.len = 0;
+ if (fd != -1)
+   if (slurpclose(fd,&cmds,256) == -1) temp_nomem();
+
+ if (!cmds.len)
+  {
+   if (!stralloc_copys(&cmds,aliasempty)) temp_nomem();
+   flagforwardonly = 0;
+  }
+ if (!cmds.len || (cmds.s[cmds.len - 1] != '\n'))
+   if (!stralloc_cats(&cmds,"\n")) temp_nomem();
+
+ numforward = 0;
+ i = 0;
+ for (j = 0;j < cmds.len;++j)
+   if (cmds.s[j] == '\n')
+    {
+     switch(cmds.s[i]) { case '#': case '.': case '/': case '|': break;
+       default: ++numforward; }
+     i = j + 1;
+    }
+
+ recips = (char **) alloc((numforward + 1) * sizeof(char *));
+ if (!recips) temp_nomem();
+ numforward = 0;
+
+ flag99 = 0;
+
+ i = 0;
+ for (j = 0;j < cmds.len;++j)
+   if (cmds.s[j] == '\n')
+    {
+     cmds.s[j] = 0;
+     k = j;
+     while ((k > i) && (cmds.s[k - 1] == ' ') || (cmds.s[k - 1] == '\t'))
+       cmds.s[--k] = 0;
+     switch(cmds.s[i])
+      {
+       case 0: /* k == i */
+        if (i) break;
+        temp_blankline();
+       case '#':
+         break;
+       case '.':
+       case '/':
+        ++count_file;
+        if (flagforwardonly) temp_fofile();
+        if (cmds.s[k - 1] == '/')
+           if (flagdoit) maildir(cmds.s + i);
+           else sayit("maildir ",cmds.s + i,k - i);
+        else
+           if (flagdoit) mailfile(cmds.s + i);
+           else sayit("mbox ",cmds.s + i,k - i);
+         break;
+       case '|':
+        ++count_program;
+        if (flagforwardonly) temp_foprog();
+         if (flagdoit) mailprogram(cmds.s + i + 1);
+         else sayit("program ",cmds.s + i + 1,k - i - 1);
+         break;
+       case '+':
+        if (str_equal(cmds.s + i + 1,"list"))
+          flagforwardonly = 1;
+        break;
+       case '&':
+         ++i;
+       default:
+        ++count_forward;
+         if (flagdoit) recips[numforward++] = cmds.s + i;
+         else sayit("forward ",cmds.s + i,k - i);
+         break;
+      }
+     i = j + 1;
+     if (flag99) break;
+    }
+
+ if (numforward) if (flagdoit)
+  {
+   recips[numforward] = 0;
+   mailforward(recips);
+  }
+
+ count_print();
+ _exit(0);
+}
diff --git a/qmail-log.5 b/qmail-log.5
new file mode 100644 (file)
index 0000000..c039156
--- /dev/null
@@ -0,0 +1,270 @@
+.TH qmail-log 5
+.SH NAME
+qmail-log \- the qmail activity record
+.SH DESCRIPTION
+.B qmail-send
+prints a series of lines describing its activities.
+Each possible line is described below.
+.SH "STARTING AND STOPPING"
+.TP 
+.B running
+.B qmail-send
+is ready to deliver messages.
+.TP
+.B local deliveries will be put on hold
+The local concurrency limit is 0, so
+.B qmail-send
+will not perform any local deliveries.
+.TP
+.B remote deliveries will be put on hold
+The remote concurrency limit is 0, so
+.B qmail-send
+will not perform any remote deliveries.
+.TP
+.B number of deliveries left before exiting: ...
+.B qmail-send
+wants to exit as soon as possible,
+usually because it was sent a
+TERM signal,
+but it has to wait for some deliveries to finish.
+It will not start any new deliveries.
+.TP
+.B exiting
+.B qmail-send
+is done.
+.SH "FATAL PROBLEMS"
+.TP
+.B alert: cannot start: ...
+.B qmail-send
+is unable to prepare itself for delivering messages;
+it is giving up.
+This normally indicates a serious configuration error,
+but it can be caused by a temporary lack of resources.
+.TP
+.B alert: oh no! lost ...
+One of the other daemons has died.
+.B qmail-send
+will exit as soon as possible.
+.SH "SERIOUS PROBLEMS"
+.TP
+.B alert: unable to append to bounce message...
+.B qmail-send
+is unable to record a permanent failure,
+usually because the disk is full.
+This is a very serious problem;
+.B qmail-send
+cannot proceed without recording the results.
+It will try again in ten seconds.
+.TP
+.B alert: out of memory...
+.B qmail-send
+tried to allocate more memory and failed.
+It will try again in ten seconds.
+.TP
+.B alert: unable to opendir...
+.B qmail-send
+is having trouble reading a file list from disk,
+usually because the system's file descriptor table is full,
+but possibly because permissions are set incorrectly.
+It will try again in ten seconds.
+.TP
+.B alert: unable to switch back...
+.B qmail-send
+was sent SIGHUP,
+and it is unable to reenter the queue directory.
+This is a very serious problem;
+.B qmail-send
+cannot proceed outside the queue directory.
+It will try again in ten seconds.
+.TP
+.B alert: unable to reread...
+.B qmail-send
+was sent SIGHUP,
+but it is unable to read the new controls.
+It will continue operating with the original controls.
+.SH "MESSAGES"
+.TP
+.B new msg \fIm\fB
+.B qmail-send
+is going to preprocess a queued message.
+The message number,
+.IR m ,
+is its disk inode number.
+After a message is removed from the queue,
+its number can be reused immediately.
+.TP
+.B info msg \fIm\fB: bytes \fIb\fB from <\fIs\fB> qp \fIq\fB uid \fIu\fB
+Message
+.I m
+contains
+.I b
+bytes;
+its envelope sender is
+.IR s ;
+it was queued by a user with user ID
+.IR u .
+.I q
+is a long-term queue identifier,
+the process ID of the
+.B qmail-queue
+that queued the message.
+.TP
+.B bounce msg \fIm\fB qp \fIq\fB
+Message
+.I m
+had some delivery failures.
+The long-term queue identifier of the bounce (or double-bounce) message
+is
+.IR q .
+.TP
+.B triple bounce: discarding ...
+Message
+.I m
+had some delivery failures,
+but it is already a double-bounce message,
+so it must be thrown away.
+Triple-bounce messages do not exist.
+.TP
+.B end msg \fIm\fB
+.B qmail-send
+is about to remove
+message
+.I m
+from the queue.
+.SH "DELIVERIES"
+.TP
+.B starting delivery \fId\fB: msg \fIm\fB to ...
+.B qmail-send
+is telling
+.B qmail-lspawn
+or
+.B qmail-rspawn
+to deliver message
+.I m
+to one recipient.
+The delivery number,
+.IR d ,
+starts at 1 and increases by 1 for each new delivery.
+.TP
+.B delivery \fId\fB: success: ...
+Delivery
+.I d
+was successful.
+.TP
+.B delivery \fId\fB: failure: ...
+Delivery
+.I d
+failed permanently.
+The message will bounce.
+.TP
+.B delivery \fId\fB: deferral: ...
+Delivery
+.I d
+failed temporarily.
+This recipient will be retried later.
+.TP
+.B delivery \fId\fB: report mangled, will defer
+There is a serious bug in
+.B qmail-lspawn
+or
+.BR qmail-rspawn .
+This recipient will be retried later.
+.SH "WARNINGS"
+.TP
+.B internal error: delivery report out of range
+.B qmail-lspawn
+or
+.B qmail-rspawn
+has supplied a report on a nonexistent delivery.
+This is a serious bug.
+.TP
+.B qmail-clean unable to clean up ...
+For some reason
+.B qmail-clean
+is unable to remove the indicated file.
+It will try again later.
+.TP
+.B trouble fsyncing ...
+.B qmail-send
+was unable to write to disk the results of preprocessing a queued message.
+It will try again later.
+.TP
+.B trouble in select
+There is an operating system bug.
+.TP
+.B trouble injecting bounce message...
+.B qmail-send
+was unable to queue a bounce message,
+usually because the disk is full.
+It will try again later.
+.TP
+.B trouble marking ...
+.B qmail-send
+was unable to record the result of a successful or permanently
+unsuccessful delivery.
+This means that the delivery will be tried again later.
+.TP
+.B trouble opening ...
+.B qmail-send
+was unable to open the list of local or remote recipients
+for a message.
+It will try again later.
+.TP
+.B trouble reading ...
+Either
+.B qmail-send
+is unable to read a recipient list,
+or it is unable to read the envelope of a queued
+message, or it is out of memory.
+Whatever it was doing, it will try again later.
+.TP
+.B trouble writing to ...
+.B qmail-send
+was unable to preprocess a queued message,
+usually because the disk is full.
+It will try again later.
+.TP
+.B unable to create ...
+.B qmail-send
+was unable to preprocess a queued message,
+usually because the disk is out of inodes.
+It will try again later.
+.TP
+.B unable to open ...
+.B qmail-send
+is unable to read the envelope of a queued message
+for preprocessing.
+It will try again later.
+.TP
+.B unable to start qmail-queue...
+.B qmail-send
+is unable to queue a bounce message,
+usually because the machine is almost out of memory.
+It will try again later.
+.TP
+.B unable to stat ...
+.B qmail-send
+is unable to obtain information about a file that should exist.
+It will try again later.
+.TP
+.B unable to unlink ...
+.B qmail-send
+is unable to remove a file.
+It will try again later.
+.TP
+.B unable to utime ...
+.B qmail-send
+is about to exit,
+and it is unable to record on disk
+the next scheduled delivery time for a message.
+The message will be retried as soon as
+.B qmail-send
+is restarted.
+.TP
+.B unknown record type in ...
+There is a serious bug in either
+.B qmail-queue
+or
+.BR qmail-send .
+.SH "SEE ALSO"
+qmail-send(8)
diff --git a/qmail-lspawn.8 b/qmail-lspawn.8
new file mode 100644 (file)
index 0000000..111329d
--- /dev/null
@@ -0,0 +1,46 @@
+.TH qmail-lspawn 8
+.SH NAME
+qmail-lspawn \- schedule local deliveries
+.SH SYNOPSIS
+.B qmail-lspawn
+.I aliasempty
+.SH DESCRIPTION
+.B qmail-lspawn
+reads a series of local delivery commands from descriptor 0,
+invokes
+.B qmail-local
+to perform the deliveries,
+and prints the results to descriptor 1.
+It passes
+.I aliasempty
+to
+.B qmail-local
+as the default delivery instruction.
+
+.B qmail-lspawn
+invokes
+.B qmail-local
+asynchronously,
+so the results may not be in the same order as the commands.
+
+For each recipient address,
+.B qmail-lspawn
+finds out which local user controls that address.
+It first checks the
+.B qmail-users
+mechanism; if the address is not listed there, it invokes
+.BR qmail-getpw .
+.B qmail-lspawn
+then runs
+.B qmail-local
+under the user's uid and gid.
+It does not set up any supplementary groups.
+
+.B qmail-lspawn
+treats an empty mailbox name as a trash address.
+.SH "SEE ALSO"
+envelopes(5),
+qmail-users(5),
+qmail-getpw(8),
+qmail-send(8),
+qmail-local(8)
diff --git a/qmail-lspawn.c b/qmail-lspawn.c
new file mode 100644 (file)
index 0000000..5109cc3
--- /dev/null
@@ -0,0 +1,234 @@
+#include "fd.h"
+#include "wait.h"
+#include "prot.h"
+#include "substdio.h"
+#include "stralloc.h"
+#include "scan.h"
+#include "exit.h"
+#include "fork.h"
+#include "error.h"
+#include "cdb.h"
+#include "case.h"
+#include "slurpclose.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "qlx.h"
+
+char *aliasempty;
+
+void initialize(argc,argv)
+int argc;
+char **argv;
+{
+  aliasempty = argv[1];
+  if (!aliasempty) _exit(100);
+}
+
+int truncreport = 3000;
+
+void report(ss,wstat,s,len)
+substdio *ss;
+int wstat;
+char *s;
+int len;
+{
+ int i;
+ if (wait_crashed(wstat))
+  { substdio_puts(ss,"Zqmail-local crashed.\n"); return; }
+ switch(wait_exitcode(wstat))
+  {
+   case QLX_CDB:
+     substdio_puts(ss,"ZTrouble reading users/cdb in qmail-lspawn.\n"); return;
+   case QLX_NOMEM:
+     substdio_puts(ss,"ZOut of memory in qmail-lspawn.\n"); return;
+   case QLX_SYS:
+     substdio_puts(ss,"ZTemporary failure in qmail-lspawn.\n"); return;
+   case QLX_NOALIAS:
+     substdio_puts(ss,"ZUnable to find alias user!\n"); return;
+   case QLX_ROOT:
+     substdio_puts(ss,"ZNot allowed to perform deliveries as root.\n"); return;
+   case QLX_USAGE:
+     substdio_puts(ss,"ZInternal qmail-lspawn bug.\n"); return;
+   case QLX_NFS:
+     substdio_puts(ss,"ZNFS failure in qmail-local.\n"); return;
+   case QLX_EXECHARD:
+     substdio_puts(ss,"DUnable to run qmail-local.\n"); return;
+   case QLX_EXECSOFT:
+     substdio_puts(ss,"ZUnable to run qmail-local.\n"); return;
+   case QLX_EXECPW:
+     substdio_puts(ss,"ZUnable to run qmail-getpw.\n"); return;
+   case 111: case 71: case 74: case 75:
+     substdio_put(ss,"Z",1); break;
+   case 0:
+     substdio_put(ss,"K",1); break;
+   case 100:
+   default:
+     substdio_put(ss,"D",1); break;
+  }
+
+ for (i = 0;i < len;++i) if (!s[i]) break;
+ substdio_put(ss,s,i);
+}
+
+stralloc lower = {0};
+stralloc nughde = {0};
+stralloc wildchars = {0};
+
+void nughde_get(local)
+char *local;
+{
+ char *(args[3]);
+ int pi[2];
+ int gpwpid;
+ int gpwstat;
+ int r;
+ int fd;
+ int flagwild;
+
+ if (!stralloc_copys(&lower,"!")) _exit(QLX_NOMEM);
+ if (!stralloc_cats(&lower,local)) _exit(QLX_NOMEM);
+ if (!stralloc_0(&lower)) _exit(QLX_NOMEM);
+ case_lowerb(lower.s,lower.len);
+
+ if (!stralloc_copys(&nughde,"")) _exit(QLX_NOMEM);
+
+ fd = open_read("users/cdb");
+ if (fd == -1)
+   if (errno != error_noent)
+     _exit(QLX_CDB);
+
+ if (fd != -1)
+  {
+   uint32 dlen;
+   unsigned int i;
+
+   r = cdb_seek(fd,"",0,&dlen);
+   if (r != 1) _exit(QLX_CDB);
+   if (!stralloc_ready(&wildchars,(unsigned int) dlen)) _exit(QLX_NOMEM);
+   wildchars.len = dlen;
+   if (cdb_bread(fd,wildchars.s,wildchars.len) == -1) _exit(QLX_CDB);
+
+   i = lower.len;
+   flagwild = 0;
+
+   do
+    {
+     /* i > 0 */
+     if (!flagwild || (i == 1) || (byte_chr(wildchars.s,wildchars.len,lower.s[i - 1]) < wildchars.len))
+      {
+       r = cdb_seek(fd,lower.s,i,&dlen);
+       if (r == -1) _exit(QLX_CDB);
+       if (r == 1)
+        {
+         if (!stralloc_ready(&nughde,(unsigned int) dlen)) _exit(QLX_NOMEM);
+         nughde.len = dlen;
+         if (cdb_bread(fd,nughde.s,nughde.len) == -1) _exit(QLX_CDB);
+         if (flagwild)
+          if (!stralloc_cats(&nughde,local + i - 1)) _exit(QLX_NOMEM);
+         if (!stralloc_0(&nughde)) _exit(QLX_NOMEM);
+         close(fd);
+         return;
+        }
+      }
+     --i;
+     flagwild = 1;
+    }
+   while (i);
+
+   close(fd);
+  }
+
+ if (pipe(pi) == -1) _exit(QLX_SYS);
+ args[0] = "bin/qmail-getpw";
+ args[1] = local;
+ args[2] = 0;
+ switch(gpwpid = vfork())
+  {
+   case -1:
+     _exit(QLX_SYS);
+   case 0:
+     if (prot_gid(auto_gidn) == -1) _exit(QLX_USAGE);
+     if (prot_uid(auto_uidp) == -1) _exit(QLX_USAGE);
+     close(pi[0]);
+     if (fd_move(1,pi[1]) == -1) _exit(QLX_SYS);
+     execv(*args,args);
+     _exit(QLX_EXECPW);
+  }
+ close(pi[1]);
+
+ if (slurpclose(pi[0],&nughde,128) == -1) _exit(QLX_SYS);
+
+ if (wait_pid(&gpwstat,gpwpid) != -1)
+  {
+   if (wait_crashed(gpwstat)) _exit(QLX_SYS);
+   if (wait_exitcode(gpwstat) != 0) _exit(wait_exitcode(gpwstat));
+  }
+}
+
+int spawn(fdmess,fdout,s,r,at)
+int fdmess; int fdout;
+char *s; char *r; int at;
+{
+ int f;
+
+ if (!(f = fork()))
+  {
+   char *(args[11]);
+   unsigned long u;
+   int n;
+   int uid;
+   int gid;
+   char *x;
+   unsigned int xlen;
+   
+   r[at] = 0;
+   if (!r[0]) _exit(0); /* <> */
+
+   if (chdir(auto_qmail) == -1) _exit(QLX_USAGE);
+
+   nughde_get(r);
+
+   x = nughde.s;
+   xlen = nughde.len;
+
+   args[0] = "bin/qmail-local";
+   args[1] = "--";
+   args[2] = x;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   scan_ulong(x,&u);
+   uid = u;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   scan_ulong(x,&u);
+   gid = u;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   args[3] = x;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   args[4] = r;
+   args[5] = x;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   args[6] = x;
+   n = byte_chr(x,xlen,0); if (n++ == xlen) _exit(QLX_USAGE); x += n; xlen -= n;
+
+   args[7] = r + at + 1;
+   args[8] = s;
+   args[9] = aliasempty;
+   args[10] = 0;
+
+   if (fd_move(0,fdmess) == -1) _exit(QLX_SYS);
+   if (fd_move(1,fdout) == -1) _exit(QLX_SYS);
+   if (fd_copy(2,1) == -1) _exit(QLX_SYS);
+   if (prot_gid(gid) == -1) _exit(QLX_USAGE);
+   if (prot_uid(uid) == -1) _exit(QLX_USAGE);
+   if (!getuid()) _exit(QLX_ROOT);
+
+   execv(*args,args);
+   if (error_temp(errno)) _exit(QLX_EXECSOFT);
+   _exit(QLX_EXECHARD);
+  }
+ return f;
+}
diff --git a/qmail-newu.9 b/qmail-newu.9
new file mode 100644 (file)
index 0000000..12f1b3f
--- /dev/null
@@ -0,0 +1,43 @@
+.TH qmail-newu 8
+.SH NAME
+qmail-newu \- prepare address assignments for qmail-lspawn
+.SH SYNOPSIS
+.B qmail-newu
+.SH DESCRIPTION
+.B qmail-newu
+reads the assignments in
+.B QMAILHOME/users/assign
+and writes them into
+.B QMAILHOME/users/cdb
+in a binary format suited
+for quick access by
+.BR qmail-lspawn .
+
+If there is a problem with
+.BR users/assign ,
+.B qmail-newu
+complains and leaves
+.B users/cdb
+alone.
+
+.B qmail-newu
+ensures that
+.B users/cdb
+is updated atomically,
+so
+.B qmail-lspawn
+never has to wait for
+.B qmail-newu
+to finish.
+However,
+.B qmail-newu
+makes no attempt to protect against two simultaneous updates of
+.BR users/cdb .
+
+The binary
+.B users/cdb
+format is portable across machines.
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8),
+qmail-pw2u(8)
diff --git a/qmail-newu.c b/qmail-newu.c
new file mode 100644 (file)
index 0000000..1f9ecc3
--- /dev/null
@@ -0,0 +1,137 @@
+#include "stralloc.h"
+#include "subfd.h"
+#include "getln.h"
+#include "substdio.h"
+#include "cdbmss.h"
+#include "exit.h"
+#include "readwrite.h"
+#include "open.h"
+#include "error.h"
+#include "case.h"
+#include "auto_qmail.h"
+
+void die_temp() { _exit(111); }
+
+void die_chdir()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to chdir\n");
+  die_temp();
+}
+void die_nomem()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: out of memory\n");
+  die_temp();
+}
+void die_opena()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to open users/assign\n");
+  die_temp();
+}
+void die_reada()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to read users/assign\n");
+  die_temp();
+}
+void die_format()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: bad format in users/assign\n");
+  die_temp();
+}
+void die_opent()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to open users/cdb.tmp\n");
+  die_temp();
+}
+void die_writet()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to write users/cdb.tmp\n");
+  die_temp();
+}
+void die_rename()
+{
+  substdio_putsflush(subfderr,"qmail-newu: fatal: unable to move users/cdb.tmp to users/cdb\n");
+  die_temp();
+}
+
+struct cdbmss cdbmss;
+stralloc key = {0};
+stralloc data = {0};
+
+char inbuf[1024];
+substdio ssin;
+
+int fd;
+int fdtemp;
+
+stralloc line = {0};
+int match;
+
+stralloc wildchars = {0};
+
+void main()
+{
+  int i;
+  int numcolons;
+
+  umask(033);
+  if (chdir(auto_qmail) == -1) die_chdir();
+
+  fd = open_read("users/assign");
+  if (fd == -1) die_opena();
+
+  substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));
+
+  fdtemp = open_trunc("users/cdb.tmp");
+  if (fdtemp == -1) die_opent();
+
+  if (cdbmss_start(&cdbmss,fdtemp) == -1) die_writet();
+
+  if (!stralloc_copys(&wildchars,"")) die_nomem();
+
+  for (;;) {
+    if (getln(&ssin,&line,&match,'\n') != 0) die_reada();
+    if (line.len && (line.s[0] == '.')) break;
+    if (!match) die_format();
+
+    if (byte_chr(line.s,line.len,'\0') < line.len) die_format();
+    i = byte_chr(line.s,line.len,':');
+    if (i == line.len) die_format();
+    if (i == 0) die_format();
+    if (!stralloc_copys(&key,"!")) die_nomem();
+    if (line.s[0] == '+') {
+      if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem();
+      case_lowerb(key.s,key.len);
+      if (i >= 2)
+       if (byte_chr(wildchars.s,wildchars.len,line.s[i - 1]) == wildchars.len)
+         if (!stralloc_append(&wildchars,line.s + i - 1)) die_nomem();
+    }
+    else {
+      if (!stralloc_catb(&key,line.s + 1,i - 1)) die_nomem();
+      if (!stralloc_0(&key)) die_nomem();
+      case_lowerb(key.s,key.len);
+    }
+
+    if (!stralloc_copyb(&data,line.s + i + 1,line.len - i - 1)) die_nomem();
+
+    numcolons = 0;
+    for (i = 0;i < data.len;++i)
+      if (data.s[i] == ':') {
+       data.s[i] = 0;
+       if (++numcolons == 6)
+         break;
+      }
+    if (numcolons < 6) die_format();
+    data.len = i;
+
+    if (cdbmss_add(&cdbmss,key.s,key.len,data.s,data.len) == -1) die_writet();
+  }
+
+  if (cdbmss_add(&cdbmss,"",0,wildchars.s,wildchars.len) == -1) die_writet();
+
+  if (cdbmss_finish(&cdbmss) == -1) die_writet();
+  if (fsync(fdtemp) == -1) die_writet();
+  if (close(fdtemp) == -1) die_writet(); /* NFS stupidity */
+  if (rename("users/cdb.tmp","users/cdb") == -1) die_rename();
+
+  _exit(0);
+}
diff --git a/qmail-pop3d.8 b/qmail-pop3d.8
new file mode 100644 (file)
index 0000000..b85abaa
--- /dev/null
@@ -0,0 +1,43 @@
+.TH qmail-pop3d 8
+.SH NAME
+qmail-pop3d \- distribute mail via POP
+.SH SYNOPSIS
+.B qmail-pop3d
+.I maildirname
+.SH DESCRIPTION
+.B qmail-pop3d
+lets a user read and delete his mail through the network.
+
+Mail is stored in a
+.B maildir
+called
+.IR maildirname ,
+normally
+.BR Maildir ,
+in the user's home directory.
+
+.B qmail-pop3d
+is normally invoked
+under
+.BR qmail-popup ,
+which reads a username and password,
+and
+.BR /bin/checkpassword ,
+which checks the password and sets up environment variables.
+
+.B qmail-pop3d
+has a 20-minute idle timeout.
+
+.B qmail-pop3d
+supports UIDL and TOP.
+
+.B qmail-pop3d
+appends an extra blank line to every message
+to work around serious bugs in certain clients.
+
+.B qmail-pop3d
+is based on a program contributed by Russ Nelson.
+.SH "SEE ALSO"
+maildir(5),
+qmail-local(8),
+qmail-popup(8)
diff --git a/qmail-pop3d.c b/qmail-pop3d.c
new file mode 100644 (file)
index 0000000..c3ff0a6
--- /dev/null
@@ -0,0 +1,397 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "direntry.h"
+#include "sig.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "alloc.h"
+#include "datetime.h"
+#include "prot.h"
+#include "open.h"
+#include "prioq.h"
+#include "scan.h"
+#include "fmt.h"
+#include "error.h"
+#include "str.h"
+#include "exit.h"
+#include "now.h"
+#include "readwrite.h"
+
+int timeout = 1200;
+
+char ssoutbuf[1024];
+substdio ssout = SUBSTDIO_FDBUF(write,1,ssoutbuf,sizeof(ssoutbuf));
+
+int timeoutread(fd,buf,n) int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ alarm(timeout);
+ r = read(fd,buf,n); saveerrno = errno;
+ alarm(0);
+ errno = saveerrno; return r;
+}
+
+char ssinbuf[128];
+substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf));
+
+
+void die() { _exit(0); }
+void puts(s) char *s;
+{
+ if (substdio_puts(&ssout,s) == -1) die();
+}
+void flush()
+{
+ if (substdio_flush(&ssout) == -1) die();
+}
+void err(s) char *s;
+{
+ puts("-ERR ");
+ puts(s);
+ puts("\r\n");
+ if (substdio_flush(&ssout) == -1) die();
+}
+void die_nomem() { err("out of memory"); die(); }
+void die_prot() { err("protection problem"); die(); }
+void die_nomaildir() { err("this user has no $HOME/Maildir"); die(); }
+
+void err_syntax() { err("syntax error"); }
+void err_unimpl() { err("unimplemented"); }
+void err_deleted() { err("already deleted"); }
+void err_nozero() { err("messages are counted from 1"); }
+void err_toobig() { err("not that many messages"); }
+void err_nosuch() { err("unable to open that message"); }
+void err_nounlink() { err("unable to unlink all deleted messages"); }
+
+void okay() { puts("+OK \r\n"); flush(); }
+void pop3_last() { puts("+OK 0\r\n"); flush(); }
+
+
+stralloc dataline = {0};
+
+stralloc filenames = {0};
+prioq pq = {0};
+stralloc newname = {0};
+
+struct message
+ {
+  int flagdeleted;
+  unsigned long size;
+  char *fn;
+ }
+*m;
+int numm;
+
+substdio ssmsg; char ssmsgbuf[1024];
+
+
+void blast(ssfrom,limit)
+substdio *ssfrom;
+unsigned long limit;
+{
+ int match;
+ int inheaders = 1;
+
+ for (;;)
+  {
+   if (getln(ssfrom,&dataline,&match,'\n') != 0) die();
+   if (!match && !dataline.len) break;
+   if (match) --dataline.len; /* no way to pass this info over POP */
+   if (limit) if (!inheaders) if (!--limit) break;
+   if (!dataline.len)
+     inheaders = 0;
+   else
+     if (dataline.s[0] == '.')
+       substdio_put(&ssout,".",1);
+   if (substdio_put(&ssout,dataline.s,dataline.len) == -1) die();
+   if (substdio_put(&ssout,"\r\n",2) == -1) die();
+   if (!match) break;
+  }
+ if (substdio_put(&ssout,"\r\n.\r\n",5) == -1) die();
+ if (substdio_flush(&ssout) == -1) die();
+}
+
+void getlist()
+{
+ unsigned long pos;
+ datetime_sec time;
+ DIR *dir;
+ direntry *d;
+ struct prioq_elt pe;
+ struct stat st;
+ int i;
+
+ numm = 0;
+
+ time = now();
+
+ if (dir = opendir("tmp"))
+  {
+   while (d = readdir(dir))
+    {
+     if (str_equal(d->d_name,".")) continue;
+     if (str_equal(d->d_name,"..")) continue;
+     if (!stralloc_copys(&newname,"tmp/")) die_nomem();
+     if (!stralloc_cats(&newname,d->d_name)) die_nomem();
+     if (!stralloc_0(&newname)) die_nomem();
+     if (stat(newname.s,&st) == 0)
+       if (time > st.st_atime + 129600)
+        unlink(newname.s);
+    }
+   closedir(dir);
+  }
+
+ if (!stralloc_copys(&filenames,"")) die_nomem();
+
+ if (dir = opendir("new"))
+  {
+   while (d = readdir(dir))
+    {
+     if (str_equal(d->d_name,".")) continue;
+     if (str_equal(d->d_name,"..")) continue;
+     pos = filenames.len;
+     if (!stralloc_cats(&filenames,"new/")) die_nomem();
+     if (!stralloc_cats(&filenames,d->d_name)) die_nomem();
+     if (!stralloc_0(&filenames)) die_nomem();
+     if (stat(filenames.s + pos,&st) == 0)
+      {
+       pe.dt = st.st_mtime;
+       pe.id = pos;
+       if (!prioq_insert(&pq,&pe)) die_nomem();
+       ++numm;
+      }
+    }
+   closedir(dir);
+  }
+
+ if (dir = opendir("cur"))
+  {
+   while (d = readdir(dir))
+    {
+     if (str_equal(d->d_name,".")) continue;
+     if (str_equal(d->d_name,"..")) continue;
+     pos = filenames.len;
+     if (!stralloc_cats(&filenames,"cur/")) die_nomem();
+     if (!stralloc_cats(&filenames,d->d_name)) die_nomem();
+     if (!stralloc_0(&filenames)) die_nomem();
+     if (stat(filenames.s + pos,&st) == 0)
+      {
+       pe.dt = st.st_mtime;
+       pe.id = pos;
+       if (!prioq_insert(&pq,&pe)) die_nomem();
+       ++numm;
+      }
+    }
+   closedir(dir);
+  }
+
+ m = (struct message *) alloc(numm * sizeof(struct message));
+ if (!m) die_nomem();
+
+ for (i = 0;i < numm;++i)
+  {
+   if (!prioq_min(&pq,&pe)) { numm = i; break; }
+   prioq_delmin(&pq);
+   m[i].fn = filenames.s + pe.id;
+   m[i].flagdeleted = 0;
+   if (stat(m[i].fn,&st) == -1)
+     m[i].size = 0;
+   else
+     m[i].size = st.st_size;
+  }
+}
+
+char foo[FMT_ULONG];
+
+void printint(u) unsigned int u;
+{
+ foo[fmt_uint(foo,u)] = 0;
+ puts(foo);
+ puts(" ");
+}
+
+void printlong(u) unsigned long u;
+{
+ foo[fmt_uint(foo,u)] = 0;
+ puts(foo);
+ puts("\r\n");
+}
+
+void printfn(fn) char *fn;
+{
+ puts(fn + 4);
+ puts("\r\n");
+}
+
+void pop3_stat()
+{
+ int i;
+ unsigned long total;
+
+ total = 0;
+ for (i = 0;i < numm;++i) if (!m[i].flagdeleted) total += m[i].size;
+ puts("+OK ");
+ printint(numm);
+ printlong(total);
+ flush();
+}
+
+void pop3_rset()
+{
+ int i;
+ for (i = 0;i < numm;++i) m[i].flagdeleted = 0;
+ okay();
+}
+
+void pop3_quit()
+{
+ int i;
+ for (i = 0;i < numm;++i)
+   if (m[i].flagdeleted)
+     if (unlink(m[i].fn) == -1) err_nounlink();
+ okay();
+ die();
+}
+
+int msgno(arg) char *arg;
+{
+ unsigned long u;
+ if (!arg) { err_syntax(); return -1; }
+ if (!scan_ulong(arg,&u)) { err_syntax(); return -1; }
+ if (!u) { err_nozero(); return -1; }
+ --u;
+ if (u >= numm) { err_toobig(); return -1; }
+ if (m[u].flagdeleted) { err_deleted(); return -1; }
+ return u;
+}
+
+void pop3_dele(arg) char *arg;
+{
+ int i;
+
+ i = msgno(arg);
+ if (i == -1) return;
+ m[i].flagdeleted = 1;
+ okay();
+}
+
+void dolisting(arg,flaguidl) char *arg; int flaguidl;
+{
+ unsigned int i;
+
+ if (arg)
+  {
+   i = msgno(arg);
+   if (i == -1) return;
+   puts("+OK ");
+   printint(i + 1);
+   if (flaguidl) printfn(m[i].fn); else printlong(m[i].size);
+  }
+ else
+  {
+   okay();
+
+   for (i = 0;i < numm;++i)
+     if (!m[i].flagdeleted)
+      {
+       printint(i + 1);
+       if (flaguidl) printfn(m[i].fn); else printlong(m[i].size);
+      }
+   puts(".\r\n");
+  }
+ flush();
+}
+
+void pop3_uidl(arg) char *arg; { dolisting(arg,1); }
+void pop3_list(arg) char *arg; { dolisting(arg,0); }
+
+void pop3_top(arg) char *arg;
+{
+ int i;
+ unsigned long limit;
+ int fd;
+
+ i = msgno(arg);
+ if (i == -1) return;
+
+ arg += scan_ulong(arg,&limit);
+ while (*arg == ' ') ++arg;
+ if (scan_ulong(arg,&limit)) ++limit; else limit = 0;
+
+ fd = open_read(m[i].fn);
+ if (fd == -1) { err_nosuch(); return; }
+ okay();
+ substdio_fdbuf(&ssmsg,read,fd,ssmsgbuf,sizeof(ssmsgbuf));
+ blast(&ssmsg,limit);
+ close(fd);
+}
+
+static struct { void (*fun)(); char *text; } pop3cmd[] = {
+  { pop3_quit, "quit" }
+, { pop3_stat, "stat" }
+, { pop3_list, "list" }
+, { pop3_uidl, "uidl" }
+, { pop3_dele, "dele" }
+, { pop3_top, "retr" }
+, { pop3_rset, "rset" }
+, { pop3_last, "last" }
+, { pop3_top, "top" }
+, { okay, "noop" }
+, { 0, 0 }
+};
+
+void doit(cmd)
+char *cmd;
+{
+ int i;
+ int j;
+ char ch;
+
+ for (i = 0;pop3cmd[i].fun;++i)
+  {
+   for (j = 0;ch = pop3cmd[i].text[j];++j)
+     if ((cmd[j] != ch) && (cmd[j] != ch - 32))
+       break;
+   if (!ch)
+     if (!cmd[j] || (cmd[j] == ' '))
+      {
+       while (cmd[j] == ' ') ++j;
+       if (!cmd[j])
+         pop3cmd[i].fun((char *) 0);
+       else
+         pop3cmd[i].fun(cmd + j);
+       return;
+      }
+  }
+ err_unimpl();
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ static stralloc cmd = {0};
+ int match;
+
+ sig_alarmcatch(die);
+ sig_pipeignore();
+
+ if (!argv[1]) die_nomaildir();
+ if (chdir(argv[1]) == -1) die_nomaildir();
+
+ getlist();
+
+ okay();
+
+ for (;;)
+  {
+   if (getln(&ssin,&cmd,&match,'\n') == -1) die();
+   if (!match) die();
+   if (cmd.len == 0) die();
+   if (cmd.s[--cmd.len] != '\n') die();
+   if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len;
+   cmd.s[cmd.len++] = 0;
+   doit(cmd.s);
+  }
+}
diff --git a/qmail-popup.8 b/qmail-popup.8
new file mode 100644 (file)
index 0000000..95f01bc
--- /dev/null
@@ -0,0 +1,65 @@
+.TH qmail-popup 8
+.SH NAME
+qmail-popup \- read a POP username and password
+.SH SYNOPSIS
+.B qmail-popup
+.I hostname
+.I subprogram
+.SH DESCRIPTION
+.B qmail-popup
+reads a POP username and password from the network.
+It then runs
+.IR subprogram .
+
+.B qmail-popup
+is most commonly invoked from
+.B inetd
+as
+
+.EX
+   qmail-popup CHANGEME checkpassword qmail-pop3d Maildir
+.EE
+
+with
+CHANGEME
+replaced by the fully qualified domain name of the local host.
+
+.B qmail-popup
+expects descriptor 0 to read from the network
+and descriptor 1 to write to the network.
+It reads a username and password from descriptor 0
+in POP's USER-PASS style or APOP style.
+It invokes
+.IR subprogram ,
+with the same descriptors 0 and 1;
+descriptor 2 writing to the network;
+and descriptor 3 reading the username, a 0 byte, the password,
+another 0 byte, 
+an APOP timestamp derived from
+.IR hostname ,
+and a final 0 byte.
+.B qmail-popup
+then waits for
+.I subprogram
+to finish.
+It prints an error message if
+.I subprogram
+crashes or exits nonzero.
+
+.B qmail-popup
+should be used only within
+a secure network.
+Otherwise an eavesdropper can steal passwords.
+Even if you use APOP,
+an active attacker can still take over the connection
+and wreak havoc.
+
+.B qmail-popup
+has a 20-minute idle timeout.
+
+.B qmail-popup
+is based on a program contributed by Russ Nelson.
+.SH "SEE ALSO"
+maildir(5),
+qmail-local(8),
+qmail-pop3d(8)
diff --git a/qmail-popup.c b/qmail-popup.c
new file mode 100644 (file)
index 0000000..f8c80a3
--- /dev/null
@@ -0,0 +1,219 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "fd.h"
+#include "sig.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "alloc.h"
+#include "datetime.h"
+#include "error.h"
+#include "wait.h"
+#include "str.h"
+#include "now.h"
+#include "fmt.h"
+#include "exit.h"
+#include "readwrite.h"
+
+int timeout = 1200;
+
+int timeoutread(fd,buf,n) int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ alarm(timeout);
+ r = read(fd,buf,n); saveerrno = errno;
+ alarm(0);
+ errno = saveerrno; return r;
+}
+
+char ssinbuf[128];
+substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf));
+
+
+void die() { _exit(1); }
+void out(s) char *s;
+{
+ if (substdio_puts(subfdoutsmall,s) == -1) die();
+}
+void outflush(s) char *s; 
+{
+ out(s);
+ if (substdio_flush(subfdoutsmall) == -1) die();
+}
+void err(s) char *s;
+{
+ if (substdio_puts(subfdoutsmall,"-ERR ") == -1) die();
+ if (substdio_puts(subfdoutsmall,s) == -1) die();
+ if (substdio_puts(subfdoutsmall,"\r\n") == -1) die();
+ if (substdio_flush(subfdoutsmall) == -1) die();
+}
+void die_usage() { err("usage: popup hostname subprogram"); die(); }
+void die_nomem() { err("out of memory"); die(); }
+void die_pipe() { err("unable to open pipe"); die(); }
+void die_write() { err("unable to write pipe"); die(); }
+void die_fork() { err("unable to fork"); die(); }
+void die_childcrashed() { err("aack, child crashed"); }
+void die_badauth() { err("authorization failed"); }
+
+void err_syntax() { err("syntax error"); }
+void err_wantuser() { err("USER first"); }
+void err_authoriz() { err("authorization first"); }
+
+void okay() { outflush("+OK \r\n"); }
+
+
+char unique[FMT_ULONG + FMT_ULONG + 3];
+char *hostname;
+stralloc username = {0};
+int seenuser = 0;
+char **childargs;
+substdio ssup;
+char upbuf[128];
+
+
+void doanddie(user,userlen,pass)
+char *user;
+unsigned int userlen; /* including 0 byte */
+char *pass;
+{
+ int child;
+ int wstat;
+ int pi[2];
+ int i;
+
+ if (fd_copy(2,1) == -1) die_pipe();
+ close(3);
+ if (pipe(pi) == -1) die_pipe();
+ if (pi[0] != 3) die_pipe();
+ switch(child = fork())
+  {
+   case -1:
+     die_fork();
+   case 0:
+     close(pi[1]);
+     sig_pipedefault();
+     execvp(*childargs,childargs);
+     _exit(1);
+  }
+ close(pi[0]);
+ substdio_fdbuf(&ssup,write,pi[1],upbuf,sizeof(upbuf));
+ if (substdio_put(&ssup,user,userlen) == -1) die_write();
+ if (substdio_put(&ssup,pass,str_len(pass) + 1) == -1) die_write();
+ if (substdio_puts(&ssup,"<") == -1) die_write();
+ if (substdio_puts(&ssup,unique) == -1) die_write();
+ if (substdio_puts(&ssup,hostname) == -1) die_write();
+ if (substdio_put(&ssup,">",2) == -1) die_write();
+ if (substdio_flush(&ssup) == -1) die_write();
+ close(pi[1]);
+ for (i = 0;pass[i];++i) pass[i] = 0;
+ for (i = 0;i < sizeof(upbuf);++i) upbuf[i] = 0;
+ if (wait_pid(&wstat,child) == -1) die();
+ if (wait_crashed(wstat)) die_childcrashed();
+ if (wait_exitcode(wstat)) die_badauth();
+ die();
+}
+void pop3_greet()
+{
+ char *s;
+ s = unique;
+ s += fmt_uint(s,getpid());
+ *s++ = '.';
+ s += fmt_ulong(s,(unsigned long) now());
+ *s++ = '@';
+ *s++ = 0;
+
+ out("+OK <");
+ out(unique);
+ out(hostname);
+ outflush(">\r\n");
+}
+void pop3_user(arg) char *arg;
+{
+ if (!arg) { err_syntax(); return; }
+ okay();
+ seenuser = 1;
+ if (!stralloc_copys(&username,arg)) die_nomem(); 
+ if (!stralloc_0(&username)) die_nomem(); 
+}
+void pop3_pass(arg) char *arg;
+{
+ if (!seenuser) { err_wantuser(); return; }
+ if (!arg) { err_syntax(); return; }
+ doanddie(username.s,username.len,arg);
+}
+void pop3_apop(arg) char *arg;
+{
+ char *space;
+ if (!arg) { err_syntax(); return; }
+ space = arg + str_chr(arg,' ');
+ if (!*space) { err_syntax(); return; }
+ *space++ = 0;
+ doanddie(arg,space - arg,space);
+}
+
+void pop3_quit() { okay(); die(); }
+
+static struct { void (*fun)(); char *text; } pop3cmd[] = {
+  { pop3_user, "user" }
+, { pop3_pass, "pass" }
+, { pop3_apop, "apop" }
+, { pop3_quit, "quit" }
+, { okay, "noop" }
+, { 0, 0 }
+};
+
+void doit(cmd)
+char *cmd;
+{
+ int i;
+ int j;
+ char ch;
+
+ for (i = 0;pop3cmd[i].fun;++i)
+  {
+   for (j = 0;ch = pop3cmd[i].text[j];++j)
+     if ((cmd[j] != ch) && (cmd[j] != ch - 32))
+       break;
+   if (!ch)
+     if (!cmd[j] || (cmd[j] == ' '))
+      {
+       while (cmd[j] == ' ') ++j;
+       if (!cmd[j])
+         pop3cmd[i].fun((char *) 0);
+       else
+         pop3cmd[i].fun(cmd + j);
+       return;
+      }
+  }
+ err_authoriz();
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ static stralloc cmd = {0};
+ int match;
+
+ sig_alarmcatch(die);
+ sig_pipeignore();
+
+ hostname = argv[1];
+ if (!hostname) die_usage();
+ childargs = argv + 2;
+ if (!*childargs) die_usage();
+
+ pop3_greet();
+
+ for (;;)
+  {
+   if (getln(&ssin,&cmd,&match,'\n') == -1) die();
+   if (!match) die();
+   if (cmd.len == 0) die();
+   if (cmd.s[--cmd.len] != '\n') die();
+   if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len;
+   cmd.s[cmd.len++] = 0;
+   doit(cmd.s);
+  }
+}
diff --git a/qmail-pw2u.9 b/qmail-pw2u.9
new file mode 100644 (file)
index 0000000..a62df8a
--- /dev/null
@@ -0,0 +1,235 @@
+.TH qmail-pw2u 8
+.SH NAME
+qmail-pw2u \- build address assignments from a passwd file
+.SH SYNOPSIS
+.B qmail-pw2u
+[
+.B \-ohHuUC
+]
+[
+.B \-c\fIchar
+]
+.SH DESCRIPTION
+.B qmail-pw2u
+reads a V7-format passwd file from standard input
+and prints a
+.BR qmail-users -format
+assignment file.
+
+A V7-format passwd file is a series of lines.
+Each line has the format
+
+.EX
+   user:password:uid:gid:gecos:home:shell
+.EE
+
+where
+.I user
+is an account name,
+.I uid
+and
+.I gid
+are the user id and group id of that account,
+and
+.I home
+is the account's home directory.
+.IR password ,
+.IR gecos ,
+and
+.I shell
+are ignored by
+.BR qmail-pw2u .
+
+If you put the output of
+.B qmail-pw2u
+into
+.BR QMAILHOME/users/assign ,
+and then run
+.BR qmail-newu ,
+.B qmail-lspawn
+will obey the assignments printed by
+.BR qmail-pw2u .
+.B WARNING:
+After changing any users, uids, gids, or home directories
+in your passwd file,
+you must run
+.B qmail-pw2u
+and
+.B qmail-newu
+again if you want
+.B qmail-lspawn
+to see the changes.
+.SH RULES
+By default,
+.B qmail-pw2u
+follows the same rules as
+.BR qmail-getpw .
+It skips
+.I user
+if (1)
+.I uid
+is zero,
+(2)
+.I home
+does not exist,
+(3)
+.I user
+does not own
+.IR home ,
+or
+(4)
+.I user
+contains uppercase letters.
+It then gives each remaining
+.I user
+control over the basic
+.I user
+address and
+all addresses of the form
+.IR user\fBBREAK\fIanything .
+A catch-all user,
+.BR alias ,
+controls all other addresses.
+
+You may change these rules by setting up files in
+.BR QMAILHOME/users :
+.TP
+.B include
+Allowed users, one per line.
+If
+.B include
+exists, and
+.I user
+is not listed in
+.BR include ,
+.I user
+is ignored.
+.TP
+.B exclude
+Ignored users, one per line.
+If
+.B exclude
+exists, and
+.I user
+is listed in
+.BR exclude ,
+.I user
+is ignored.
+.TP
+.B mailnames
+Replacement names for users.
+Each line has the form
+
+.EX
+   user:mailname1:mailname2:...
+.EE
+
+The addresses
+.I mailname1
+and
+.I mailname1\fBBREAK\fIext
+and
+.I mailname2
+and so on will be delivered
+to
+.IR user .
+
+.B WARNING:
+The addresses
+.I user
+and
+.I user\fBBREAK\fIext
+will not be delivered to
+.I user
+unless
+.I user
+is listed as one of the
+.IR mailname s.
+
+A line in
+.B mailnames
+is silently ignored if the user does not exist.
+.TP
+.B subusers
+Extra addresses.
+Each line has the form
+
+.EX
+   sub:user:pre:
+.EE
+
+.I sub
+will be handled by
+.IR home\fB/.qmail-\fIpre ,
+where
+.I home
+is
+.IR user 's
+home directory;
+.I sub\fBBREAK\fIext
+will be handled by
+.IR home\fB/.qmail-\fIpre\fB-\fIext .
+.TP
+.B append
+Extra assignments,
+printed at the end of
+.BR qmail-pw2u 's
+output.
+.SH OPTIONS
+.TP
+.B \-o
+(Default.)
+Skip
+.I user
+if
+.I home
+does not exist (or is not visible to
+.BR qmail-pw2u ).
+Skip
+.I user
+if
+.I home
+is not owned by
+.IR user .
+.TP
+.B \-h
+Stop if
+.I home
+does not exist.
+This is appropriate if every user is supposed to have a home directory.
+Skip
+.I user
+if
+.I home
+is not owned by
+.IR user .
+.TP
+.B \-H
+Do not check the existence or ownership of
+.IR home .
+.TP
+.B \-U
+(Default.)
+Skip
+.I user
+if there are any uppercase letters in
+.IR user .
+.TP
+.B \-u
+Allow uppercase letters in
+.IR user .
+.TP
+.B \-c\fIchar
+Use
+.I char
+as the user-extension delimiter
+in place of
+.BR BREAK .
+.TP
+.B \-C
+Disable the user-extension mechanism.
+.SH "SEE ALSO"
+qmail-users(5),
+qmail-lspawn(8),
+qmail-newu(8),
+qmail-getpw(8)
diff --git a/qmail-pw2u.c b/qmail-pw2u.c
new file mode 100644 (file)
index 0000000..5889f1d
--- /dev/null
@@ -0,0 +1,308 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "substdio.h"
+#include "readwrite.h"
+#include "subfd.h"
+#include "sgetopt.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "fmt.h"
+#include "str.h"
+#include "scan.h"
+#include "open.h"
+#include "error.h"
+#include "getln.h"
+#include "auto_break.h"
+#include "auto_qmail.h"
+#include "auto_usera.h"
+
+void die_chdir()
+{
+  substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to chdir\n");
+  _exit(111);
+}
+void die_nomem()
+{
+  substdio_putsflush(subfderr,"qmail-pw2u: fatal: out of memory\n");
+  _exit(111);
+}
+void die_read()
+{
+  substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to read input\n");
+  _exit(111);
+}
+void die_write()
+{
+  substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to write output\n");
+  _exit(111);
+}
+void die_control()
+{
+  substdio_putsflush(subfderr,"qmail-pw2u: fatal: unable to read controls\n");
+  _exit(111);
+}
+void die_alias()
+{
+  substdio_puts(subfderr,"qmail-pw2u: fatal: unable to find ");
+  substdio_puts(subfderr,auto_usera);
+  substdio_puts(subfderr," user\n");
+  substdio_flush(subfderr);
+  _exit(111);
+}
+void die_home(fn) char *fn;
+{
+  substdio_puts(subfderr,"qmail-pw2u: fatal: unable to stat ");
+  substdio_puts(subfderr,fn);
+  substdio_puts(subfderr,"\n");
+  substdio_flush(subfderr);
+  _exit(111);
+}
+void die_user(s,len) char *s; unsigned int len;
+{
+  substdio_puts(subfderr,"qmail-pw2u: fatal: unable to find ");
+  substdio_put(subfderr,s,len);
+  substdio_puts(subfderr," user for subuser\n");
+  substdio_flush(subfderr);
+  _exit(111);
+}
+
+int flagalias = 0;
+int flagnoupper = 1;
+int homestrategy = 2;
+/* 2: skip if home does not exist; skip if home is not owned by user */
+/* 1: stop if home does not exist; skip if home is not owned by user */
+/* 0: don't worry about home */
+
+int okincl; stralloc incl = {0}; struct constmap mapincl;
+int okexcl; stralloc excl = {0}; struct constmap mapexcl;
+int okmana; stralloc mana = {0}; struct constmap mapmana;
+
+stralloc allusers = {0}; struct constmap mapuser;
+
+stralloc uugh = {0};
+stralloc user = {0};
+stralloc uidstr = {0};
+stralloc gidstr = {0};
+stralloc home = {0};
+unsigned long uid;
+
+stralloc line = {0};
+
+void doaccount()
+{
+  struct stat st;
+  int i;
+  char *mailnames;
+  char *x;
+  unsigned int xlen;
+
+  if (byte_chr(line.s,line.len,'\0') < line.len) return;
+
+  x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  if (!stralloc_copyb(&user,x,i)) die_nomem();
+  if (!stralloc_0(&user)) die_nomem();
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  if (!stralloc_copyb(&uidstr,x,i)) die_nomem();
+  if (!stralloc_0(&uidstr)) die_nomem();
+  scan_ulong(uidstr.s,&uid);
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  if (!stralloc_copyb(&gidstr,x,i)) die_nomem();
+  if (!stralloc_0(&gidstr)) die_nomem();
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  if (!stralloc_copyb(&home,x,i)) die_nomem();
+  if (!stralloc_0(&home)) die_nomem();
+
+  if (!uid) return;
+  if (flagnoupper)
+    for (i = 0;i < user.len;++i)
+      if ((user.s[i] >= 'A') && (user.s[i] <= 'Z'))
+       return;
+  if (okincl)
+    if (!constmap(&mapincl,user.s,user.len - 1))
+      return;
+  if (okexcl)
+    if (constmap(&mapexcl,user.s,user.len - 1))
+      return;
+  if (homestrategy) {
+    if (stat(home.s,&st) == -1) {
+      if (errno != error_noent) die_home(home.s);
+      if (homestrategy == 1) die_home(home.s);
+      return;
+    }
+    if (st.st_uid != uid) return;
+  }
+
+  if (!stralloc_copys(&uugh,":")) die_nomem();
+  if (!stralloc_cats(&uugh,user.s)) die_nomem();
+  if (!stralloc_cats(&uugh,":")) die_nomem();
+  if (!stralloc_cats(&uugh,uidstr.s)) die_nomem();
+  if (!stralloc_cats(&uugh,":")) die_nomem();
+  if (!stralloc_cats(&uugh,gidstr.s)) die_nomem();
+  if (!stralloc_cats(&uugh,":")) die_nomem();
+  if (!stralloc_cats(&uugh,home.s)) die_nomem();
+  if (!stralloc_cats(&uugh,":")) die_nomem();
+
+  /* XXX: avoid recording in allusers unless sub actually needs it */
+  if (!stralloc_cats(&allusers,user.s)) die_nomem();
+  if (!stralloc_cats(&allusers,":")) die_nomem();
+  if (!stralloc_catb(&allusers,uugh.s,uugh.len)) die_nomem();
+  if (!stralloc_0(&allusers)) die_nomem();
+
+  if (str_equal(user.s,auto_usera)) {
+    if (substdio_puts(subfdout,"+") == -1) die_write();
+    if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write();
+    if (substdio_puts(subfdout,"-::\n") == -1) die_write();
+    flagalias = 1;
+  }
+
+  mailnames = 0;
+  if (okmana)
+    mailnames = constmap(&mapmana,user.s,user.len - 1);
+  if (!mailnames)
+    mailnames = user.s;
+
+  for (;;) {
+    while (*mailnames == ':') ++mailnames;
+    if (!*mailnames) break;
+
+    i = str_chr(mailnames,':');
+
+    if (substdio_puts(subfdout,"=") == -1) die_write();
+    if (substdio_put(subfdout,mailnames,i) == -1) die_write();
+    if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write();
+    if (substdio_puts(subfdout,"::\n") == -1) die_write();
+  
+    if (*auto_break) {
+      if (substdio_puts(subfdout,"+") == -1) die_write();
+      if (substdio_put(subfdout,mailnames,i) == -1) die_write();
+      if (substdio_put(subfdout,auto_break,1) == -1) die_write();
+      if (substdio_put(subfdout,uugh.s,uugh.len) == -1) die_write();
+      if (substdio_puts(subfdout,"-::\n") == -1) die_write();
+    }
+
+    mailnames += i;
+  }
+}
+
+stralloc sub = {0};
+
+void dosubuser()
+{
+  int i;
+  char *x;
+  unsigned int xlen;
+  char *uugh;
+
+  x = line.s; xlen = line.len; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  if (!stralloc_copyb(&sub,x,i)) die_nomem();
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+  uugh = constmap(&mapuser,x,i);
+  if (!uugh) die_user(x,i);
+  ++i; x += i; xlen -= i; i = byte_chr(x,xlen,':'); if (i == xlen) return;
+
+  if (substdio_puts(subfdout,"=") == -1) die_write();
+  if (substdio_put(subfdout,sub.s,sub.len) == -1) die_write();
+  if (substdio_puts(subfdout,uugh) == -1) die_write();
+  if (substdio_puts(subfdout,"-:") == -1) die_write();
+  if (substdio_put(subfdout,x,i) == -1) die_write();
+  if (substdio_puts(subfdout,":\n") == -1) die_write();
+
+  if (*auto_break) {
+    if (substdio_puts(subfdout,"+") == -1) die_write();
+    if (substdio_put(subfdout,sub.s,sub.len) == -1) die_write();
+    if (substdio_put(subfdout,auto_break,1) == -1) die_write();
+    if (substdio_puts(subfdout,uugh) == -1) die_write();
+    if (substdio_puts(subfdout,"-:") == -1) die_write();
+    if (substdio_put(subfdout,x,i) == -1) die_write();
+    if (substdio_puts(subfdout,"-:\n") == -1) die_write();
+  }
+}
+
+int fd;
+substdio ss;
+char ssbuf[SUBSTDIO_INSIZE];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  int opt;
+  int match;
+
+  while ((opt = getopt(argc,argv,"ohHuUc:C")) != opteof)
+    switch(opt) {
+      case 'o': homestrategy = 2; break;
+      case 'h': homestrategy = 1; break;
+      case 'H': homestrategy = 0; break;
+      case 'u': flagnoupper = 0; break;
+      case 'U': flagnoupper = 1; break;
+      case 'c': *auto_break = *optarg; break;
+      case 'C': *auto_break = 0; break;
+      case '?':
+      default:
+       _exit(100);
+    }
+
+  if (chdir(auto_qmail) == -1) die_chdir();
+
+  /* no need for control_init() */
+
+  okincl = control_readfile(&incl,"users/include",0);
+  if (okincl == -1) die_control();
+  if (okincl) if (!constmap_init(&mapincl,incl.s,incl.len,0)) die_nomem();
+
+  okexcl = control_readfile(&excl,"users/exclude",0);
+  if (okexcl == -1) die_control();
+  if (okexcl) if (!constmap_init(&mapexcl,excl.s,excl.len,0)) die_nomem();
+
+  okmana = control_readfile(&mana,"users/mailnames",0);
+  if (okmana == -1) die_control();
+  if (okmana) if (!constmap_init(&mapmana,mana.s,mana.len,1)) die_nomem();
+
+  if (!stralloc_copys(&allusers,"")) die_nomem();
+
+  for (;;) {
+    if (getln(subfdin,&line,&match,'\n') == -1) die_read();
+    doaccount();
+    if (!match) break;
+  }
+  if (!flagalias) die_alias();
+
+  fd = open_read("users/subusers");
+  if (fd == -1) {
+    if (errno != error_noent) die_control();
+  }
+  else {
+    substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
+
+    if (!constmap_init(&mapuser,allusers.s,allusers.len,1)) die_nomem();
+
+    for (;;) {
+      if (getln(&ss,&line,&match,'\n') == -1) die_read();
+      dosubuser();
+      if (!match) break;
+    }
+
+    close(fd);
+  }
+
+  fd = open_read("users/append");
+  if (fd == -1) {
+    if (errno != error_noent) die_control();
+  }
+  else {
+    substdio_fdbuf(&ss,read,fd,ssbuf,sizeof(ssbuf));
+    for (;;) {
+      if (getln(&ss,&line,&match,'\n') == -1) die_read();
+      if (substdio_put(subfdout,line.s,line.len) == -1) die_write();
+      if (!match) break;
+    }
+  }
+
+  if (substdio_puts(subfdout,".\n") == -1) die_write();
+  if (substdio_flush(subfdout) == -1) die_write();
+  _exit(0);
+}
diff --git a/qmail-qmtpd.8 b/qmail-qmtpd.8
new file mode 100644 (file)
index 0000000..0b52452
--- /dev/null
@@ -0,0 +1,29 @@
+.TH qmail-qmtpd 8
+.SH NAME
+qmail-qmtpd \- receive mail via QMTP
+.SH SYNOPSIS
+.B qmail-qmtpd
+.SH DESCRIPTION
+.B qmail-qmtpd
+receives mail messages via the Quick Mail Transfer Protocol (QMTP)
+and invokes
+.B qmail-queue
+to deposit them into the outgoing queue.
+.B qmail-qmtpd
+must be supplied several environment variables;
+see
+.BR tcp-environ(5) .
+
+.B qmail-qmtpd
+supports the
+.I rcpthosts
+and
+.B RELAYCLIENT
+mechanisms described in
+.BR qmail-smtpd(8) .
+.SH "SEE ALSO"
+tcp-env(1),
+tcp-environ(5),
+qmail-control(5),
+qmail-queue(8),
+qmail-smtpd(8)
diff --git a/qmail-qmtpd.c b/qmail-qmtpd.c
new file mode 100644 (file)
index 0000000..fab2a0d
--- /dev/null
@@ -0,0 +1,281 @@
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "qmail.h"
+#include "now.h"
+#include "str.h"
+#include "fmt.h"
+#include "env.h"
+#include "sig.h"
+#include "auto_qmail.h"
+#include "now.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "readwrite.h"
+#include "control.h"
+#include "constmap.h"
+#include "received.h"
+
+struct qmail qqt;
+
+void dropped() { _exit(0); }
+void badproto() { _exit(100); }
+void resources() { _exit(111); }
+void sigalrm() { _exit(111); }
+
+unsigned long getlen()
+{
+ unsigned long len;
+ char ch;
+
+ len = 0;
+ for (;;)
+  {
+   if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+   if (ch == ':') return len;
+   if (len > 200000000) resources();
+   len = 10 * len + (ch - '0');
+  }
+}
+
+void getcomma()
+{
+ char ch;
+
+ if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+ if (ch != ',') badproto();
+}
+
+struct datetime dt;
+char buf[1000];
+char buf2[100];
+
+char *remotehost;
+char *remoteinfo;
+char *remoteip;
+char *local;
+
+stralloc failure = {0};
+
+int flagrcpthosts;
+stralloc rcpthosts = {0};
+struct constmap maprcpthosts;
+char *relayclient;
+int relayclientlen;
+
+int addrallowed(buf,len) char *buf; int len;
+{
+ int j;
+ if (!flagrcpthosts) return 1;
+ j = byte_rchr(buf,len,'@');
+ if (j >= len) return 1;
+ if (constmap(&maprcpthosts,buf + j + 1,len - j - 1)) return 1;
+ for (;j < len;++j)
+   if (buf[j] == '.')
+     if (constmap(&maprcpthosts,buf + j,len - j)) return 1;
+ return 0;
+}
+
+main()
+{
+ char ch;
+ int i;
+ unsigned long biglen;
+ unsigned long len;
+ int flagdos;
+ int flagsenderok;
+ unsigned long qp;
+ char *result;
+
+ sig_pipeignore();
+ sig_alarmcatch(sigalrm);
+ alarm(3600);
+
+ if (chdir(auto_qmail) == -1) resources();
+
+ if (control_init() == -1) resources();
+ flagrcpthosts = control_readfile(&rcpthosts,"control/rcpthosts",0);
+ if (flagrcpthosts == -1) resources();
+ if (flagrcpthosts)
+   if (!constmap_init(&maprcpthosts,rcpthosts.s,rcpthosts.len,0)) resources();
+ relayclient = env_get("RELAYCLIENT");
+ relayclientlen = relayclient ? str_len(relayclient) : 0;
+
+ remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCPREMOTEINFO");
+ remoteip = env_get("TCPREMOTEIP");
+ if (!remoteip) remoteip = "unknown";
+ local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+
+ for (;;)
+  {
+   if (!stralloc_copys(&failure,"")) resources();
+   flagsenderok = 1;
+
+   len = getlen();
+   if (len == 0) badproto();
+
+   if (qmail_open(&qqt) == -1) resources();
+   qp = qmail_qp(&qqt);
+
+   if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+   --len;
+
+   if (ch == 10) flagdos = 0;
+   else if (ch == 13) flagdos = 1;
+   else badproto();
+
+   received(&qqt,"QMTP",local,remoteip,remotehost,remoteinfo,(char *) 0);
+
+   /* XXX: check for loops? only if len is big? */
+
+   if (flagdos)
+     while (len > 0)
+      {
+       if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+       --len;
+       while ((ch == 13) && len)
+        {
+         if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+         --len;
+         if (ch == 10) break;
+         qmail_put(&qqt,"\015",1);
+        }
+       qmail_put(&qqt,&ch,1);
+      }
+   else
+     while (len > 0) /* XXX: could speed this up, obviously */
+      {
+       if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+       --len;
+       qmail_put(&qqt,&ch,1);
+      }
+   getcomma();
+
+   len = getlen();
+
+   if (len >= 1000)
+    {
+     buf[0] = 0;
+     flagsenderok = 0;
+     for (i = 0;i < len;++i)
+       if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+    }
+   else
+    {
+     for (i = 0;i < len;++i)
+      {
+       if (substdio_get(subfdinsmall,buf + i,1) < 1) dropped();
+       if (!buf[i]) flagsenderok = 0;
+      }
+     buf[len] = 0;
+    }
+   getcomma();
+
+   qmail_from(&qqt,buf);
+   if (!flagsenderok) qmail_fail(&qqt);
+
+   biglen = getlen();
+   while (biglen > 0)
+    {
+     if (!stralloc_append(&failure,"")) resources();
+
+     len = 0;
+     for (;;)
+      {
+       if (!biglen) badproto();
+       if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+       --biglen;
+       if (ch == ':') break;
+       if (len > 200000000) resources();
+       len = 10 * len + (ch - '0');
+      }
+     if (len >= biglen) badproto();
+     if (len + relayclientlen >= 1000)
+      {
+       failure.s[failure.len - 1] = 'L';
+       for (i = 0;i < len;++i)
+         if (substdio_get(subfdinsmall,&ch,1) < 1) dropped();
+      }
+     else
+      {
+       for (i = 0;i < len;++i)
+        {
+         if (substdio_get(subfdinsmall,buf + i,1) < 1) dropped();
+         if (!buf[i]) failure.s[failure.len - 1] = 'N';
+        }
+       buf[len] = 0;
+
+       if (relayclient)
+        str_copy(buf + len,relayclient);
+       else
+        if (!addrallowed(buf,len)) failure.s[failure.len - 1] = 'D';
+
+       if (!failure.s[failure.len - 1])
+        qmail_to(&qqt,buf);
+      }
+     getcomma();
+     biglen -= (len + 1);
+    }
+   getcomma();
+
+   switch(qmail_close(&qqt))
+    {
+     case 0: result = 0; break;
+     case QMAIL_WAITPID: result = "Zqq waitpid surprise (#4.3.0)"; break;
+     case QMAIL_CRASHED: result = "Zqq crashed (#4.3.0)"; break;
+     case QMAIL_USAGE: result = "Zqq usage surprise (#4.3.0)"; break;
+     case QMAIL_SYS: result = "Zqq system error (#4.3.0)"; break;
+     case QMAIL_READ: result = "Zqq read error (#4.3.0)"; break;
+     case QMAIL_WRITE: result = "Zqq write error or disk full (#4.3.0)"; break;
+     case QMAIL_NOMEM: result = "Zqq out of memory (#4.3.0)"; break;
+     case QMAIL_EXECSOFT: result = "Zcould not exec qq (#4.3.0)"; break;
+     case QMAIL_TIMEOUT: result = "Zqq timeout (#4.3.0)"; break;
+     case QMAIL_TOOLONG: result = "Dqq toolong surprise (#5.1.3)"; break;
+     default: result = "Zqq internal bug (#4.3.0)"; break;
+    }
+
+   if (!flagsenderok) result = "Dunacceptable sender (#5.1.7)";
+
+   if (result)
+     len = str_len(result);
+   else
+    {
+     /* success! */
+     len = 0;
+     len += fmt_str(buf2 + len,"Kok ");
+     len += fmt_ulong(buf2 + len,(unsigned long) now());
+     len += fmt_str(buf2 + len," qp ");
+     len += fmt_ulong(buf2 + len,qp);
+     buf2[len] = 0;
+     result = buf2;
+    }
+     
+   len = fmt_ulong(buf,len);
+   buf[len++] = ':';
+   len += fmt_str(buf + len,result);
+   buf[len++] = ',';
+
+   for (i = 0;i < failure.len;++i)
+     switch(failure.s[i])
+      {
+       case 0:
+         if (substdio_put(subfdoutsmall,buf,len) == -1)
+           dropped();
+        break;
+       case 'D':
+         if (substdio_puts(subfdoutsmall,"66:Dsorry, that domain isn't in my list of allowed rcpthosts (#5.7.1),") == -1)
+          dropped();
+        break;
+       default:
+         if (substdio_puts(subfdoutsmall,"46:Dsorry, I can't handle that recipient (#5.1.3),") == -1)
+          dropped();
+        break;
+      }
+
+   /* subfdoutsmall will be flushed when we read from the network again */
+  }
+}
diff --git a/qmail-qread.8 b/qmail-qread.8
new file mode 100644 (file)
index 0000000..a4c31ef
--- /dev/null
@@ -0,0 +1,24 @@
+.TH qmail-qread 8
+.SH NAME
+qmail-qread \- list outgoing messages and recipients
+.SH SYNOPSIS
+.B qmail-qread
+.SH DESCRIPTION
+.B qmail-qread
+scans the outgoing queue of messages.
+For each message it prints various human-readable information,
+including the date the message entered the queue,
+the number of bytes in the message,
+the message sender,
+and all the recipients still under consideration.
+
+.B qmail-qread
+must be run either as 
+.B root
+or with user id
+.B qmails
+and group id
+.BR qmail .
+.SH "SEE ALSO"
+qmail-qstat(8),
+qmail-send(8)
diff --git a/qmail-qread.c b/qmail-qread.c
new file mode 100644 (file)
index 0000000..8ec0fbd
--- /dev/null
@@ -0,0 +1,175 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "fmt.h"
+#include "str.h"
+#include "getln.h"
+#include "fmtqfn.h"
+#include "readsubdir.h"
+#include "auto_qmail.h"
+#include "open.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "readwrite.h"
+#include "error.h"
+#include "exit.h"
+
+readsubdir rs;
+
+void die(n) int n; { substdio_flush(subfdout); _exit(n); }
+
+void warn(s1,s2) char *s1; char *s2;
+{
+ char *x;
+ x = error_str(errno);
+ substdio_puts(subfdout,s1);
+ substdio_puts(subfdout,s2);
+ substdio_puts(subfdout,": ");
+ substdio_puts(subfdout,x);
+ substdio_puts(subfdout,"\n");
+}
+
+void die_nomem() { substdio_puts(subfdout,"fatal: out of memory\n"); die(111); }
+void die_chdir() { warn("fatal: unable to chdir",""); die(111); }
+void die_opendir(fn) char *fn; { warn("fatal: unable to opendir ",fn); die(111); }
+
+void err(id) unsigned long id;
+{
+ char foo[FMT_ULONG];
+ foo[fmt_ulong(foo,id)] = 0;
+ warn("warning: trouble with #",foo);
+}
+
+char fnmess[FMTQFN];
+char fninfo[FMTQFN];
+char fnlocal[FMTQFN];
+char fnremote[FMTQFN];
+char fnbounce[FMTQFN];
+
+char inbuf[1024];
+stralloc sender = {0};
+
+unsigned long id;
+datetime_sec qtime;
+int flagbounce;
+unsigned long size;
+
+unsigned int fmtstats(s)
+char *s;
+{
+ struct datetime dt;
+ unsigned int len;
+ unsigned int i;
+
+ len = 0;
+ datetime_tai(&dt,qtime);
+ i = date822fmt(s,&dt) - 7/*XXX*/; len += i; if (s) s += i;
+ i = fmt_str(s," GMT  #"); len += i; if (s) s += i;
+ i = fmt_ulong(s,id); len += i; if (s) s += i;
+ i = fmt_str(s,"  "); len += i; if (s) s += i;
+ i = fmt_ulong(s,size); len += i; if (s) s += i;
+ i = fmt_str(s,"  <"); len += i; if (s) s += i;
+ i = fmt_str(s,sender.s + 1); len += i; if (s) s += i;
+ i = fmt_str(s,"> "); len += i; if (s) s += i;
+ if (flagbounce)
+  {
+   i = fmt_str(s," bouncing"); len += i; if (s) s += i;
+  }
+
+ return len;
+}
+
+stralloc stats = {0};
+
+void out(s,n) char *s; unsigned int n;
+{
+ while (n > 0)
+  {
+   substdio_put(subfdout,((*s >= 32) && (*s <= 126)) ? s : "_",1);
+   --n;
+   ++s;
+  }
+}
+void outs(s) char *s; { out(s,str_len(s)); }
+void outok(s) char *s; { substdio_puts(subfdout,s); }
+
+void putstats()
+{
+ if (!stralloc_ready(&stats,fmtstats(FMT_LEN))) die_nomem();
+ stats.len = fmtstats(stats.s);
+ out(stats.s,stats.len);
+ outok("\n");
+}
+
+stralloc line = {0};
+
+void main()
+{
+ int channel;
+ int match;
+ struct stat st;
+ int fd;
+ substdio ss;
+ int x;
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+ if (chdir("queue") == -1) die_chdir();
+ readsubdir_init(&rs,"info",die_opendir);
+
+ while (x = readsubdir_next(&rs,&id))
+   if (x > 0)
+    {
+     fmtqfn(fnmess,"mess/",id,1);
+     fmtqfn(fninfo,"info/",id,1);
+     fmtqfn(fnlocal,"local/",id,1);
+     fmtqfn(fnremote,"remote/",id,1);
+     fmtqfn(fnbounce,"bounce/",id,0);
+
+     if (stat(fnmess,&st) == -1) { err(id); continue; }
+     size = st.st_size;
+     flagbounce = !stat(fnbounce,&st);
+
+     fd = open_read(fninfo);
+     if (fd == -1) { err(id); continue; }
+     substdio_fdbuf(&ss,read,fd,inbuf,sizeof(inbuf));
+     if (getln(&ss,&sender,&match,0) == -1) die_nomem();
+     if (fstat(fd,&st) == -1) { close(fd); err(id); continue; }
+     close(fd);
+     qtime = st.st_mtime;
+
+     putstats();
+
+     for (channel = 0;channel < 2;++channel)
+      {
+       fd = open_read(channel ? fnremote : fnlocal);
+       if (fd == -1)
+       {
+        if (errno != error_noent)
+          err(id);
+       }
+       else
+        {
+         for (;;)
+         {
+          if (getln(&ss,&line,&match,0) == -1) die_nomem();
+          if (!match) break;
+          switch(line.s[0])
+           {
+            case 'D':
+              outok("  done");
+            case 'T':
+              outok(channel ? "\tremote\t" : "\tlocal\t");
+              outs(line.s + 1);
+              outok("\n");
+              break;
+           }
+         }
+         close(fd);
+        }
+      }
+    }
+
+ die(0);
+}
diff --git a/qmail-qstat.8 b/qmail-qstat.8
new file mode 100644 (file)
index 0000000..3491e34
--- /dev/null
@@ -0,0 +1,18 @@
+.TH qmail-qstat 8
+.SH NAME
+qmail-qstat \- summarize status of mail queue
+.SH SYNOPSIS
+.B qmail-qstat
+.SH DESCRIPTION
+.B qmail-qstat
+gives a human-readable breakdown
+of the number of messages at various spots in the mail queue.
+
+.B qmail-qstat
+must be run either as
+.B root
+or with group id
+.BR qmail .
+.SH "SEE ALSO"
+qmail-qread(8),
+qmail-send(8)
diff --git a/qmail-qstat.sh b/qmail-qstat.sh
new file mode 100644 (file)
index 0000000..21a8c00
--- /dev/null
@@ -0,0 +1,3 @@
+cd QMAIL
+echo messages in queue: `find queue/mess -type f -print | wc -l`
+echo messages in queue but not yet preprocessed: `find queue/todo -type f -print | wc -l`
diff --git a/qmail-queue.8 b/qmail-queue.8
new file mode 100644 (file)
index 0000000..33ca546
--- /dev/null
@@ -0,0 +1,58 @@
+.TH qmail-queue 8
+.SH NAME
+qmail-queue \- queue a mail message for delivery
+.SH SYNOPSIS
+.B qmail-queue
+.SH DESCRIPTION
+.B qmail-queue
+reads a mail message from descriptor 0.
+It then reads envelope information from descriptor 1.
+It places the message into the outgoing queue
+for future delivery by
+.BR qmail-send .
+
+The envelope information is
+an envelope sender address
+followed by a list of envelope recipient addresses.
+The sender address is preceded by the letter F
+and terminated by a 0 byte.
+Each recipient address is preceded by the letter T
+and terminated by a 0 byte.
+The list of recipient addresses is terminated by an extra 0 byte.
+If
+.B qmail-queue
+sees end-of-file before the extra 0 byte,
+it aborts without placing the message into the queue.
+
+Every envelope recipient address
+must contain a username,
+an @ sign,
+and a fully qualified domain name.
+
+.B qmail-queue
+always adds a
+.B Received
+line to the top of the message.
+Other than this,
+.B qmail-queue
+does not inspect the message
+and does not enforce any restrictions on its contents.
+However, the recipients probably expect to see a proper header,
+as described in
+.BR qmail-header(5) .
+.SH "EXIT CODES"
+0 if
+.B qmail-queue
+has successfully queued the message,
+nonzero if
+.B qmail-queue
+has failed to queue the message.
+.B qmail-queue
+does not print diagnostics.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-header(5),
+qmail-inject(8),
+qmail-send(8),
+qmail-smtpd(8)
diff --git a/qmail-queue.c b/qmail-queue.c
new file mode 100644 (file)
index 0000000..26483b2
--- /dev/null
@@ -0,0 +1,254 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "readwrite.h"
+#include "sig.h"
+#include "exit.h"
+#include "open.h"
+#include "seek.h"
+#include "fmt.h"
+#include "alloc.h"
+#include "substdio.h"
+#include "datetime.h"
+#include "now.h"
+#include "triggerpull.h"
+#include "extra.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "date822fmt.h"
+#include "fmtqfn.h"
+
+#define DEATH 86400 /* 24 hours; _must_ be below q-s's OSSIFIED (36 hours) */
+#define ADDR 1003
+
+char inbuf[2048];
+struct substdio ssin;
+char outbuf[256];
+struct substdio ssout;
+
+datetime_sec starttime;
+struct datetime dt;
+unsigned long mypid;
+unsigned long uid;
+char *pidfn;
+struct stat pidst;
+unsigned long messnum;
+char *messfn;
+char *todofn;
+char *intdfn;
+int messfd;
+int intdfd;
+int flagmademess = 0;
+int flagmadeintd = 0;
+
+void cleanup()
+{
+ if (flagmadeintd)
+  {
+   seek_trunc(intdfd,0);
+   if (unlink(intdfn) == -1) return;
+  }
+ if (flagmademess)
+  {
+   seek_trunc(messfd,0);
+   if (unlink(messfn) == -1) return;
+  }
+}
+
+void die(e) int e; { _exit(e); }
+void die_write() { cleanup(); die(122); }
+void die_read() { cleanup(); die(121); }
+void sigalrm() { /* thou shalt not clean up here */ die(124); }
+void sigbug() { die(101); }
+
+unsigned int receivedlen;
+char *received;
+/* "Received: (qmail-queue invoked by alias); 26 Sep 1995 04:46:54 -0000\n" */
+
+static unsigned int receivedfmt(s)
+char *s;
+{
+ unsigned int i;
+ unsigned int len;
+ len = 0;
+ i = fmt_str(s,"Received: (qmail "); len += i; if (s) s += i;
+ i = fmt_ulong(s,mypid); len += i; if (s) s += i;
+ i = fmt_str(s," invoked "); len += i; if (s) s += i;
+ if (uid == auto_uida)
+  { i = fmt_str(s,"by alias"); len += i; if (s) s += i; }
+ else if (uid == auto_uidd)
+  { i = fmt_str(s,"from network"); len += i; if (s) s += i; }
+ else if (uid == auto_uids)
+  { i = fmt_str(s,"for bounce"); len += i; if (s) s += i; }
+ else
+  {
+   i = fmt_str(s,"by uid "); len += i; if (s) s += i;
+   i = fmt_ulong(s,uid); len += i; if (s) s += i;
+  }
+ i = fmt_str(s,"); "); len += i; if (s) s += i;
+ i = date822fmt(s,&dt); len += i; if (s) s += i;
+ return len;
+}
+
+void received_setup()
+{
+ receivedlen = receivedfmt((char *) 0);
+ received = alloc(receivedlen + 1);
+ if (!received) die(123);
+ receivedfmt(received);
+}
+
+unsigned int pidfmt(s,seq)
+char *s;
+unsigned long seq;
+{
+ unsigned int i;
+ unsigned int len;
+
+ len = 0;
+ i = fmt_str(s,"pid/"); len += i; if (s) s += i;
+ i = fmt_ulong(s,mypid); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,starttime); len += i; if (s) s += i;
+ i = fmt_str(s,"."); len += i; if (s) s += i;
+ i = fmt_ulong(s,seq); len += i; if (s) s += i;
+ ++len; if (s) *s++ = 0;
+
+ return len;
+}
+
+char *fnnum(dirslash,flagsplit)
+char *dirslash;
+int flagsplit;
+{
+ char *s;
+
+ s = alloc(fmtqfn((char *) 0,dirslash,messnum,flagsplit));
+ if (!s) die(123);
+ fmtqfn(s,dirslash,messnum,flagsplit);
+ return s;
+}
+
+void pidopen()
+{
+ unsigned int len;
+ unsigned long seq;
+
+ seq = 1;
+ len = pidfmt((char *) 0,seq);
+ pidfn = alloc(len);
+ if (!pidfn) die(123);
+
+ for (seq = 1;seq < 10;++seq)
+  {
+   if (pidfmt((char *) 0,seq) > len) die(101); /* paranoia */
+   pidfmt(pidfn,seq);
+   messfd = open_excl(pidfn);
+   if (messfd != -1) return;
+  }
+
+ die(103);
+}
+
+char tmp[FMT_ULONG];
+
+void main()
+{
+ unsigned int len;
+ char ch;
+
+ sig_blocknone();
+ umask(033);
+ if (chdir(auto_qmail) == -1) die(102);
+ if (chdir("queue") == -1) die(102);
+
+ mypid = getpid();
+ uid = getuid();
+ starttime = now();
+ datetime_tai(&dt,starttime);
+
+ received_setup();
+
+ sig_pipeignore();
+ sig_miscignore();
+ sig_alarmcatch(sigalrm);
+ sig_bugcatch(sigbug);
+
+ alarm(DEATH);
+
+ pidopen();
+ if (fstat(messfd,&pidst) == -1) die(104);
+
+ messnum = pidst.st_ino;
+ messfn = fnnum("mess/",1);
+ todofn = fnnum("todo/",0);
+ intdfn = fnnum("intd/",0);
+
+ if (link(pidfn,messfn) == -1) die(105);
+ if (unlink(pidfn) == -1) die(105);
+ flagmademess = 1;
+
+ substdio_fdbuf(&ssout,write,messfd,outbuf,sizeof(outbuf));
+ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+
+ if (substdio_bput(&ssout,received,receivedlen) == -1) die_write();
+
+ switch(substdio_copy(&ssout,&ssin))
+  {
+   case -2: die_read();
+   case -3: die_write();
+  }
+
+ if (substdio_flush(&ssout) == -1) die_write();
+ if (fsync(messfd) == -1) die_write();
+
+ intdfd = open_excl(intdfn);
+ if (intdfd == -1) die(108);
+ flagmadeintd = 1;
+
+ substdio_fdbuf(&ssout,write,intdfd,outbuf,sizeof(outbuf));
+ substdio_fdbuf(&ssin,read,1,inbuf,sizeof(inbuf));
+
+ if (substdio_bput(&ssout,"u",1) == -1) die_write();
+ if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,uid)) == -1) die_write();
+ if (substdio_bput(&ssout,"",1) == -1) die_write();
+
+ if (substdio_bput(&ssout,"p",1) == -1) die_write();
+ if (substdio_bput(&ssout,tmp,fmt_ulong(tmp,mypid)) == -1) die_write();
+ if (substdio_bput(&ssout,"",1) == -1) die_write();
+
+ if (substdio_get(&ssin,&ch,1) < 1) die_read();
+ if (ch != 'F') die(112);
+ if (substdio_bput(&ssout,&ch,1) == -1) die_write();
+ for (len = 0;len < ADDR;++len)
+  {
+   if (substdio_get(&ssin,&ch,1) < 1) die_read();
+   if (substdio_put(&ssout,&ch,1) == -1) die_write();
+   if (!ch) break;
+  }
+ if (len >= ADDR) die(115);
+
+ if (substdio_bput(&ssout,QUEUE_EXTRA,QUEUE_EXTRALEN) == -1) die_write();
+
+ for (;;)
+  {
+   if (substdio_get(&ssin,&ch,1) < 1) die_read();
+   if (!ch) break;
+   if (ch != 'T') die(112);
+   if (substdio_bput(&ssout,&ch,1) == -1) die_write();
+   for (len = 0;len < ADDR;++len)
+    {
+     if (substdio_get(&ssin,&ch,1) < 1) die_read();
+     if (substdio_bput(&ssout,&ch,1) == -1) die_write();
+     if (!ch) break;
+    }
+   if (len >= ADDR) die(115);
+  }
+
+ if (substdio_flush(&ssout) == -1) die_write();
+ if (fsync(intdfd) == -1) die_write();
+
+ if (link(intdfn,todofn) == -1) die(106);
+
+ triggerpull();
+ die(0);
+}
diff --git a/qmail-remote.8 b/qmail-remote.8
new file mode 100644 (file)
index 0000000..552ac92
--- /dev/null
@@ -0,0 +1,206 @@
+.TH qmail-remote 8
+.SH NAME
+qmail-remote \- send mail via SMTP
+.SH SYNOPSIS
+.B qmail-remote
+.I host
+.I sender
+.I recip
+[
+.I recip ...
+]
+.SH DESCRIPTION
+.B qmail-remote
+reads a mail message from its input
+and sends the message
+to one or more recipients
+at a remote host.
+
+The remote host is
+.BR qmail-remote 's
+first argument,
+.IR host .
+.B qmail-remote
+sends the message to
+.IR host ,
+or to a mail exchanger for
+.I host
+listed in the Domain Name System,
+via the Simple Mail Transfer Protocol (SMTP).
+.I host
+can be either a fully-qualified domain name:
+
+.EX
+     silverton.berkeley.edu
+.EE
+
+or an IP address enclosed in brackets:
+
+.EX
+     [128.32.183.163]
+.EE
+
+The envelope recipient addresses are listed as
+.I recip
+arguments to
+.BR qmail-remote .
+The envelope sender address is listed as
+.I sender\fP.
+
+Note that
+.B qmail-remote
+does not take options
+and does not follow the
+.B getopt
+standard.
+.SH TRANSPARENCY
+End-of-file in SMTP is encoded as dot CR LF.
+A dot at the beginning of a line is encoded as dot dot.
+It is impossible in SMTP to send a message that does not end with a newline.
+.B qmail-remote
+converts the UNIX newline convention into the SMTP newline convention
+by inserting CR before each LF.
+
+It is a violation of the SMTP protocol
+to send a message that contains long lines or non-ASCII characters.
+However,
+.B qmail-remote
+will happily send such messages.
+It is the user's responsibility to avoid generating illegal messages.
+.SH "RESULTS"
+.B qmail-remote
+prints some number of 
+.I recipient reports\fP,
+followed by a
+.I message report\fR.
+Each report is terminated by a 0 byte.
+Each report begins with a single letter:
+.TP 5
+r
+Recipient report: acceptance.
+.TP 5
+h
+Recipient report: permanent rejection.
+.TP 5
+s
+Recipient report: temporary rejection.
+.TP 5
+K
+Message report: success.
+.I host
+has taken responsibility for delivering the message to each
+acceptable recipient.
+.TP 5
+Z
+Message report: temporary failure.
+.TP 5
+D
+Message report: permanent failure.
+.PP
+After this letter comes a human-readable description of
+what happened.
+
+The recipient reports will always be printed in the same order as
+.BR qmail-remote 's
+.I recip
+arguments.
+Note that in failure cases there may be fewer
+recipient reports
+than
+.I recip
+arguments.
+
+.B qmail-remote
+always exits zero.
+.SH "CONTROL FILES"
+.TP 5
+.I helohost
+Current host name,
+for use solely in saying hello to the remote SMTP server.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-remote
+refuses to run.
+.TP 5
+.I smtproutes
+Artificial SMTP routes.
+Each route has the form
+.IR domain\fB:\fIrelay ,
+without any extra spaces.
+If
+.I domain
+matches
+.IR host ,
+.B qmail-remote
+will connect to
+.IR relay ,
+as if
+.I host
+had
+.I relay
+as its only MX.
+(It will also avoid doing any CNAME lookups on
+.I sender
+and
+.IR recip .)
+.I host
+may include a colon and a port number to use instead of the
+normal SMTP port, 25:
+
+.EX
+   inside.af.mil:firewall.af.mil:26
+.EE
+
+.I relay
+may be empty;
+this tells
+.B qmail-remote
+to look up MX records as usual.
+.I smtproutes
+may include wildcards:
+
+.EX
+   .af.mil:
+   :heaven.af.mil
+.EE
+
+Here
+any address ending with
+.B .af.mil
+(but not
+.B af.mil
+itself)
+is routed by its MX records;
+any other address is artificially routed to
+.BR heaven.af.mil .
+
+The
+.B qmail
+system does not protect you if you create an artificial
+mail loop between machines.
+However,
+you are always safe using
+.I smtproutes
+if you do not accept mail from the network.
+.TP 5
+.I timeoutconnect
+Number of seconds
+.B qmail-remote
+will wait for the remote SMTP server to accept a connection.
+Default: 60.
+The kernel normally imposes a 75-second upper limit.
+.TP 5
+.I timeoutremote
+Number of seconds
+.B qmail-remote
+will wait for each response from the remote SMTP server.
+Default: 1200.
+.SH "SEE ALSO"
+addresses(5),
+envelopes(5),
+qmail-control(5),
+qmail-send(8),
+qmail-smtpd(8),
+qmail-tcpto(8)
diff --git a/qmail-remote.c b/qmail-remote.c
new file mode 100644 (file)
index 0000000..4495b69
--- /dev/null
@@ -0,0 +1,480 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "sig.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "scan.h"
+#include "case.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "dns.h"
+#include "alloc.h"
+#include "quote.h"
+#include "ip.h"
+#include "ipalloc.h"
+#include "ipme.h"
+#include "gen_alloc.h"
+#include "gen_allocdefs.h"
+#include "str.h"
+#include "now.h"
+#include "exit.h"
+#include "constmap.h"
+#include "tcpto.h"
+#include "timeoutconn.h"
+#include "timeoutread.h"
+#include "timeoutwrite.h"
+
+#define HUGESMTPTEXT 5000
+
+#define PORT_SMTP 25 /* silly rabbit, /etc/services is for users */
+unsigned long port = PORT_SMTP;
+
+GEN_ALLOC_typedef(saa,stralloc,sa,len,a)
+GEN_ALLOC_readyplus(saa,stralloc,sa,len,a,i,n,x,10,saa_readyplus)
+static stralloc sauninit = {0};
+
+stralloc helohost = {0};
+stralloc routes = {0};
+struct constmap maproutes;
+stralloc host = {0};
+stralloc sender = {0};
+
+saa reciplist = {0};
+
+struct ip_address partner;
+
+void out(s) char *s; { if (substdio_puts(subfdout,s) == -1) _exit(0); }
+void zero() { if (substdio_put(subfdout,"\0",1) == -1) _exit(0); }
+void zerodie() { zero(); substdio_flush(subfdout); _exit(0); }
+
+void outsafe(sa) stralloc *sa; { int i; char ch;
+for (i = 0;i < sa->len;++i) {
+ch = sa->s[i]; if (ch < 33) ch = '?'; if (ch > 126) ch = '?';
+if (substdio_put(subfdout,&ch,1) == -1) _exit(0); } }
+
+void temp_nomem() { out("ZOut of memory. (#4.3.0)\n"); zerodie(); }
+void temp_oserr() { out("Z\
+System resources temporarily unavailable. (#4.3.0)\n"); zerodie(); }
+void temp_noconn() { out("Z\
+Sorry, I wasn't able to establish an SMTP connection. (#4.4.1)\n"); zerodie(); }
+void temp_read() { out("ZUnable to read message. (#4.3.0)\n"); zerodie(); }
+void temp_dnscanon() { out("Z\
+CNAME lookup failed temporarily. (#4.4.3)\n"); zerodie(); }
+void temp_dns() { out("Z\
+Sorry, I couldn't find any host by that name. (#4.1.2)\n"); zerodie(); }
+void temp_chdir() { out("Z\
+Unable to switch to home directory. (#4.3.0)\n"); zerodie(); }
+void temp_control() { out("Z\
+Unable to read control files. (#4.3.0)\n"); zerodie(); }
+void perm_partialline() { out("D\
+SMTP cannot transfer messages with partial final lines. (#5.6.2)\n"); zerodie(); }
+void perm_usage() { out("D\
+I (qmail-remote) was invoked improperly. (#5.3.5)\n"); zerodie(); }
+void perm_dns() { out("D\
+Sorry, I couldn't find any host named ");
+outsafe(&host);
+out(". (#5.1.2)\n"); zerodie(); }
+void perm_nomx() { out("D\
+Sorry, I couldn't find a mail exchanger or IP address. (#5.4.4)\n");
+zerodie(); }
+void perm_ambigmx() { out("D\
+Sorry. Although I'm listed as a best-preference MX or A for that host,\n\
+it isn't in my control/locals file, so I don't treat it as local. (#5.4.6)\n");
+zerodie(); }
+
+int timeout = 1200;
+int timeoutconnect = 60;
+
+void getcontrols()
+{
+ int r;
+ if (control_init() == -1)
+  { if (errno == error_nomem) temp_nomem(); temp_control(); }
+
+ if (control_readint(&timeout,"control/timeoutremote") == -1)
+  { if (errno == error_nomem) temp_nomem(); temp_control(); }
+ if (control_readint(&timeoutconnect,"control/timeoutconnect") == -1)
+  { if (errno == error_nomem) temp_nomem(); temp_control(); }
+
+ r = control_rldef(&helohost,"control/helohost",1,(char *) 0);
+ if (r == -1) if (errno == error_nomem) temp_nomem();
+ if (r != 1) temp_control();
+
+ switch(control_readfile(&routes,"control/smtproutes",0))
+  {
+   case -1:
+     if (errno == error_nomem) temp_nomem(); temp_control();
+   case 0:
+     if (!constmap_init(&maproutes,"",0,1)) temp_nomem(); break;
+   case 1:
+     if (!constmap_init(&maproutes,routes.s,routes.len,1)) temp_nomem(); break;
+  }
+}
+
+char smtptobuf[1024];
+char smtpfrombuf[128];
+stralloc smtpline = {0};
+stralloc smtptext = {0};
+
+void outsmtptext()
+{
+ int i; 
+ if (smtptext.s) if (smtptext.len) if (smtptext.len < HUGESMTPTEXT)
+  {
+   if (substdio_puts(subfdout,"Remote host said: ") == -1) _exit(0);
+   for (i = 0;i < smtptext.len;++i)
+     if (!smtptext.s[i]) smtptext.s[i] = '?';
+   if (substdio_put(subfdout,smtptext.s,smtptext.len) == -1) _exit(0);
+   smtptext.len = 0;
+  }
+}
+
+unsigned long smtpcode(ss)
+substdio *ss;
+{
+ int match;
+ unsigned long code;
+
+ if (!stralloc_copys(&smtptext,"")) return 421;
+ do
+  {
+   if (getln(ss,&smtpline,&match,'\n') != 0) return 421;
+   if (!match) return 421;
+   if ((smtpline.len >= 2) && (smtpline.s[smtpline.len - 2] == '\r'))
+    {
+     smtpline.s[smtpline.len - 2] = '\n';
+     --smtpline.len;
+    }
+   if (!stralloc_cat(&smtptext,&smtpline)) return 421;
+   if (scan_nbblong(smtpline.s,smtpline.len,10,0,&code) != 3) return 421;
+   if (smtpline.len == 3) return code;
+  }
+ while (smtpline.s[3] == '-');
+
+ return code;
+}
+
+void outhost()
+{
+ char x[IPFMT];
+
+ x[ip_fmt(x,&partner)] = 0;
+ out(x);
+}
+
+void writeerr()
+{
+ out("ZConnected to "); outhost();
+ out(" but communications failed. (#4.4.2)\n");
+ zerodie();
+}
+
+void quit(ssto,ssfrom)
+substdio *ssto;
+substdio *ssfrom;
+{
+ outsmtptext();
+ if (substdio_putsflush(ssto,"QUIT\r\n") != -1)
+   smtpcode(ssfrom); /* protocol design stupidity */
+ zerodie();
+}
+
+stralloc dataline = {0};
+
+void blast(ssto,ssfrom)
+substdio *ssto;
+substdio *ssfrom;
+{
+ int match;
+
+ for (;;)
+  {
+   if (getln(ssfrom,&dataline,&match,'\n') != 0) temp_read();
+   if (!match && !dataline.len) break;
+   if (!match) perm_partialline();
+   --dataline.len;
+   if (dataline.len && (dataline.s[0] == '.'))
+     if (substdio_put(ssto,".",1) == -1) writeerr();
+   if (substdio_put(ssto,dataline.s,dataline.len) == -1) writeerr();
+   if (substdio_put(ssto,"\r\n",2) == -1) writeerr();
+  }
+ if (substdio_put(ssto,".\r\n",3) == -1) writeerr();
+ if (substdio_flush(ssto) == -1) writeerr();
+}
+
+stralloc recip = {0};
+
+void smtp(fd)
+int fd;
+{
+ substdio ssto;
+ substdio ssfrom;
+ unsigned long code;
+ int flaganyrecipok;
+ int i;
+
+ substdio_fdbuf(&ssto,timeoutwrite,TIMEOUTWRITE(timeout,fd),smtptobuf,sizeof(smtptobuf));
+ substdio_fdbuf(&ssfrom,timeoutread,TIMEOUTREAD(timeout,fd),smtpfrombuf,sizeof(smtpfrombuf));
+
+ if (smtpcode(&ssfrom) != 220)
+  {
+   out("ZConnected to "); outhost(); out(" but greeting failed.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ if (substdio_puts(&ssto,"HELO ") == -1) writeerr();
+ if (substdio_put(&ssto,helohost.s,helohost.len) == -1) writeerr();
+ if (substdio_puts(&ssto,"\r\n") == -1) writeerr();
+ if (substdio_flush(&ssto) == -1) writeerr();
+
+ if (smtpcode(&ssfrom) != 250)
+  {
+   out("ZConnected to "); outhost(); out(" but my name was rejected.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ if (substdio_puts(&ssto,"MAIL FROM:<") == -1) writeerr();
+ if (substdio_put(&ssto,sender.s,sender.len) == -1) writeerr();
+ if (substdio_puts(&ssto,">\r\n") == -1) writeerr();
+ if (substdio_flush(&ssto) == -1) writeerr();
+
+ code = smtpcode(&ssfrom);
+ if (code >= 500)
+  {
+   out("DConnected to "); outhost(); out(" but sender was rejected.\n");
+   quit(&ssto,&ssfrom);
+  }
+ if (code >= 400)
+  {
+   out("ZConnected to "); outhost(); out(" but sender was rejected.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ flaganyrecipok = 0;
+ for (i = 0;i < reciplist.len;++i)
+  {
+   if (substdio_puts(&ssto,"RCPT TO:<") == -1) writeerr();
+   if (substdio_put(&ssto,reciplist.sa[i].s,reciplist.sa[i].len) == -1) writeerr();
+   if (substdio_puts(&ssto,">\r\n") == -1) writeerr();
+   if (substdio_flush(&ssto) == -1) writeerr();
+
+   code = smtpcode(&ssfrom);
+   if (code == 421)
+    {
+     out("ZConnected to "); outhost(); out(" but connection died.\n");
+     quit(&ssto,&ssfrom);
+    }
+   if (code >= 500)
+    {
+     out("h"); outhost(); out(" does not like recipient.\n");
+     outsmtptext(); zero();
+    }
+   else if (code >= 400)
+    {
+     out("s"); outhost(); out(" does not like recipient.\n");
+     outsmtptext(); zero();
+    }
+   else
+    {
+     out("r"); zero();
+     flaganyrecipok = 1;
+    }
+  }
+
+ if (!flaganyrecipok)
+  {
+   out("DGiving up.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ if (substdio_putsflush(&ssto,"DATA\r\n") == -1) writeerr();
+
+ code = smtpcode(&ssfrom);
+ if (code == 421)
+  {
+   out("ZConnected to "); outhost(); out(" but connection died.\n");
+   quit(&ssto,&ssfrom);
+  }
+ if (code >= 500)
+  {
+   out("D"); outhost(); out(" failed on DATA command.\n");
+   quit(&ssto,&ssfrom);
+  }
+ if (code >= 400)
+  {
+   out("Z"); outhost(); out(" failed on DATA command.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ blast(&ssto,subfdin);
+
+ code = smtpcode(&ssfrom);
+ if (code == 421)
+  {
+   out("ZConnected to "); outhost(); out(" but connection died. Possible duplicate!\n");
+   quit(&ssto,&ssfrom);
+  }
+ if (code >= 500)
+  {
+   out("D"); outhost(); out(" failed after I sent the message.\n");
+   quit(&ssto,&ssfrom);
+  }
+ if (code >= 400)
+  {
+   out("Z"); outhost(); out(" failed after I sent the message.\n");
+   quit(&ssto,&ssfrom);
+  }
+
+ out("K"); outhost(); out(" accepted message.\n");
+ quit(&ssto,&ssfrom);
+}
+
+stralloc canonhost = {0};
+stralloc canonbox = {0};
+
+void addrmangle(saout,s,flagalias,flagcname)
+stralloc *saout; /* host has to be canonical, box has to be quoted */
+char *s;
+int *flagalias;
+int flagcname;
+{
+ int j;
+
+ *flagalias = flagcname;
+
+ j = str_rchr(s,'@');
+ if (!s[j])
+  {
+   if (!stralloc_copys(saout,s)) temp_nomem();
+   return;
+  }
+ if (!stralloc_copys(&canonbox,s)) temp_nomem();
+ canonbox.len = j;
+ if (!quote(saout,&canonbox)) temp_nomem();
+ if (!stralloc_cats(saout,"@")) temp_nomem();
+
+ if (!stralloc_copys(&canonhost,s + j + 1)) temp_nomem();
+ if (flagcname)
+   switch(dns_cname(&canonhost))
+    {
+     case 0: *flagalias = 0; break;
+     case DNS_MEM: temp_nomem();
+     case DNS_SOFT: temp_dnscanon();
+     case DNS_HARD: ; /* alias loop, not our problem */
+    }
+
+ if (!stralloc_cat(saout,&canonhost)) temp_nomem();
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ static ipalloc ip = {0};
+ int i;
+ unsigned long random;
+ char **recips;
+ unsigned long prefme;
+ int flagallaliases;
+ int flagalias;
+ char *relayhost;
+
+ sig_pipeignore();
+ if (argc < 4) perm_usage();
+ if (chdir(auto_qmail) == -1) temp_chdir();
+ getcontrols();
+
+
+ if (!stralloc_copys(&host,argv[1])) temp_nomem();
+
+ relayhost = 0;
+ for (i = 0;i <= host.len;++i)
+   if ((i == 0) || (i == host.len) || (host.s[i] == '.'))
+     if (relayhost = constmap(&maproutes,host.s + i,host.len - i))
+       break;
+ if (relayhost && !*relayhost) relayhost = 0;
+
+ if (relayhost)
+  {
+   i = str_chr(relayhost,':');
+   if (relayhost[i])
+    {
+     scan_ulong(relayhost + i + 1,&port);
+     relayhost[i] = 0;
+    }
+   if (!stralloc_copys(&host,relayhost)) temp_nomem();
+  }
+
+
+ addrmangle(&sender,argv[2],&flagalias,!relayhost);
+
+ if (!saa_readyplus(&reciplist,0)) temp_nomem();
+ if (ipme_init() != 1) temp_oserr();
+
+ flagallaliases = 1;
+ recips = argv + 3;
+ while (*recips)
+  {
+   if (!saa_readyplus(&reciplist,1)) temp_nomem();
+   reciplist.sa[reciplist.len] = sauninit;
+   addrmangle(reciplist.sa + reciplist.len,*recips,&flagalias,!relayhost);
+   if (!flagalias) flagallaliases = 0;
+   ++reciplist.len;
+   ++recips;
+  }
+
+
+ random = now() + (getpid() << 16);
+ switch (relayhost ? dns_ip(&ip,&host) : dns_mxip(&ip,&host,random))
+  {
+   case DNS_MEM: temp_nomem();
+   case DNS_SOFT: temp_dns();
+   case DNS_HARD: perm_dns();
+   case 1:
+     if (ip.len <= 0) temp_dns();
+  }
+
+ if (ip.len <= 0) perm_nomx();
+
+ prefme = 100000;
+ for (i = 0;i < ip.len;++i)
+   if (ipme_is(&ip.ix[i].ip))
+     if (ip.ix[i].pref < prefme)
+       prefme = ip.ix[i].pref;
+
+ if (relayhost) prefme = 300000;
+ if (flagallaliases) prefme = 500000;
+
+ for (i = 0;i < ip.len;++i)
+   if (ip.ix[i].pref < prefme)
+     break;
+
+ if (i >= ip.len)
+   perm_ambigmx();
+
+ for (i = 0;i < ip.len;++i) if (ip.ix[i].pref < prefme)
+  {
+   int s;
+
+   if (tcpto(&ip.ix[i].ip)) continue;
+
+   s = socket(AF_INET,SOCK_STREAM,0);
+   if (s == -1) temp_oserr();
+
+   if (timeoutconn(s,&ip.ix[i].ip,(unsigned int) port,timeoutconnect) == 0)
+    {
+     tcpto_err(&ip.ix[i].ip,0);
+     partner = ip.ix[i].ip;
+     smtp(s); /* does not return */
+    }
+   tcpto_err(&ip.ix[i].ip,errno == error_timeout);
+   close(s);
+  }
+ temp_noconn();
+}
diff --git a/qmail-rspawn.8 b/qmail-rspawn.8
new file mode 100644 (file)
index 0000000..33e8d0d
--- /dev/null
@@ -0,0 +1,21 @@
+.TH qmail-rspawn 8
+.SH NAME
+qmail-rspawn \- schedule remote deliveries
+.SH SYNOPSIS
+.B qmail-rspawn
+.SH DESCRIPTION
+.B qmail-rspawn
+reads a series of remote delivery commands from descriptor 0,
+invokes
+.B qmail-remote
+to perform the deliveries,
+and prints the results to descriptor 1.
+
+.B qmail-rspawn
+invokes
+.B qmail-remote
+asynchronously,
+so the results may not be in the same order as the commands.
+.SH "SEE ALSO"
+qmail-send(8),
+qmail-remote(8)
diff --git a/qmail-rspawn.c b/qmail-rspawn.c
new file mode 100644 (file)
index 0000000..9d838e6
--- /dev/null
@@ -0,0 +1,103 @@
+#include "fd.h"
+#include "wait.h"
+#include "substdio.h"
+#include "exit.h"
+#include "fork.h"
+#include "error.h"
+#include "tcpto.h"
+
+void initialize(argc,argv)
+int argc;
+char **argv;
+{
+ tcpto_clean();
+}
+
+int truncreport = 0;
+
+void report(ss,wstat,s,len)
+substdio *ss;
+int wstat;
+char *s;
+int len;
+{
+ int j;
+ int k;
+ int result;
+ int orr;
+
+ if (wait_crashed(wstat))
+  { substdio_puts(ss,"Zqmail-remote crashed.\n"); return; }
+ switch(wait_exitcode(wstat))
+  {
+   case 0: break;
+   case 111: substdio_puts(ss,"ZUnable to run qmail-remote.\n"); return;
+   default: substdio_puts(ss,"DUnable to run qmail-remote.\n"); return;
+  }
+ if (!len)
+  { substdio_puts(ss,"Zqmail-remote produced no output.\n"); return; }
+
+ result = -1;
+ j = 0;
+ for (k = 0;k < len;++k)
+   if (!s[k])
+    {
+     if (s[j] == 'K') { result = 1; break; }
+     if (s[j] == 'Z') { result = 0; break; }
+     if (s[j] == 'D') break;
+     j = k + 1;
+    }
+
+ orr = result;
+ switch(s[0])
+  {
+   case 's': orr = 0; break;
+   case 'h': orr = -1;
+  }
+
+ switch(orr)
+  {
+   case 1: substdio_put(ss,"K",1); break;
+   case 0: substdio_put(ss,"Z",1); break;
+   case -1: substdio_put(ss,"D",1); break;
+  }
+
+ for (k = 1;k < len;)
+   if (!s[k++])
+    {
+     substdio_puts(ss,s + 1);
+     if (result <= orr)
+       if (k < len)
+        switch(s[k])
+         {
+          case 'Z': case 'D': case 'K':
+             substdio_puts(ss,s + k + 1);
+         }
+     break;
+    }
+}
+
+int spawn(fdmess,fdout,s,r,at)
+int fdmess; int fdout;
+char *s; char *r; int at;
+{
+ int f;
+ char *(args[5]);
+
+ args[0] = "qmail-remote";
+ args[1] = r + at + 1;
+ args[2] = s;
+ args[3] = r;
+ args[4] = 0;
+
+ if (!(f = vfork()))
+  {
+   if (fd_move(0,fdmess) == -1) _exit(111);
+   if (fd_move(1,fdout) == -1) _exit(111);
+   if (fd_copy(2,1) == -1) _exit(111);
+   execvp(*args,args);
+   if (error_temp(errno)) _exit(111);
+   _exit(100);
+  }
+ return f;
+}
diff --git a/qmail-send.9 b/qmail-send.9
new file mode 100644 (file)
index 0000000..3be8602
--- /dev/null
@@ -0,0 +1,264 @@
+.TH qmail-send 8
+.SH NAME
+qmail-send \- deliver mail messages from the queue
+.SH SYNOPSIS
+.B qmail-send
+.SH DESCRIPTION
+.B qmail-send
+handles messages placed into the outgoing queue by
+.BR qmail-queue .
+It uses
+.B qmail-lspawn
+to deliver messages to local recipients and
+.B qmail-rspawn
+to deliver messages to remote recipients.
+If a message is temporarily undeliverable to one or more addresses,
+.B qmail-send
+leaves it in the queue and tries the addresses again later.
+
+.B qmail-send
+prints a readable record of its activities to descriptor 0.
+It writes commands to
+.BR qmail-lspawn ,
+.BR qmail-rspawn ,
+and
+.B qmail-clean
+on descriptors 1, 3, and 5,
+and reads responses from descriptors 2, 4, and 6.
+.B qmail-send
+is responsible for avoiding deadlock.
+
+If
+.B qmail-send
+receives a TERM signal,
+it will exit cleanly, after waiting
+(possibly more than a minute)
+for current delivery attempts to finish.
+
+If
+.B qmail-send
+receives an ALRM signal,
+it will reschedule every message in the queue for immediate delivery.
+.SH "CONTROL FILES"
+.B WARNING:
+.B qmail-send
+reads its control files only when it starts.
+If you change the control files,
+you must stop and restart
+.BR qmail-send .
+Exception:
+If
+.B qmail-send
+receives a HUP signal,
+it will reread
+.I locals
+and
+.IR virtualdomains .
+.TP 5
+.I bouncefrom
+Bounce username.
+Default:
+.BR MAILER-DAEMON .
+.TP 5
+.I bouncehost
+Bounce host.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR bouncehost ,
+which is probably not what you want.
+If a message is permanently undeliverable,
+.B qmail-send
+sends a
+.B single-bounce
+notice back to the message's envelope sender.
+The notice is
+.B From: \fIbouncefrom\fB@\fIbouncehost\fR,
+although its envelope sender is empty.
+.TP 5
+.I concurrencylocal
+Maximum number of simultaneous local delivery attempts.
+Default: 10.
+If 0, local deliveries will be put on hold.
+.I concurrencylocal
+is limited at compile time to
+SPAWN.
+.TP 5
+.I concurrencyremote
+Maximum number of simultaneous remote delivery attempts.
+Default: 20.
+If 0, remote deliveries will be put on hold.
+.I concurrencyremote
+is limited at compile time to
+SPAWN.
+.TP 5
+.I doublebouncehost
+Double-bounce host.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR doublebouncehost ,
+which is probably not what you want.
+.TP 5
+.I doublebounceto
+User to receive double-bounces.
+Default:
+.BR postmaster .
+If a single-bounce notice is permanently undeliverable,
+.B qmail-send
+sends a
+.B double-bounce
+notice to
+.IR doublebounceto\fB@\fIdoublebouncehost .
+(If that bounces,
+.B qmail-send
+gives up.)
+.TP 5
+.I envnoathost
+Presumed domain name for addresses without @ signs.
+Default:
+.IR me ,
+if that is supplied;
+otherwise the literal name
+.BR envnoathost ,
+which is probably not what you want.
+If
+.B qmail-send
+sees an envelope recipient address without an @ sign,
+it appends
+.B @\fIenvnoathost\fR.
+.TP 5
+.I locals
+List of domain names that the current host
+receives mail for,
+one per line.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-send
+refuses to run.
+An address 
+.I user@domain
+is considered local if
+.I domain
+is listed in
+.IR locals .
+.TP 5
+.I percenthack
+List of domain names where the percent hack is applied.
+If
+.I domain
+is listed in
+.IR percenthack ,
+any address of the form
+.I user%fqdn@domain
+is rewritten as
+.IR user@fqdn .
+.I user
+may contain %,
+so the percent hack may be applied repeatedly.
+.B qmail-send
+handles
+.I percenthack
+before
+.IR recipientmap .
+.TP 5
+.I queuelifetime
+Number of seconds
+a message can stay in the queue.
+Default: 604800 (one week).
+After this time expires,
+.B qmail-send
+will try the message once more,
+but it will treat any temporary delivery failures as
+permanent failures.
+.TP 5
+.I recipientmap
+List of redirections, one per line.
+Each redirection has the form
+.IR recipient\fB:\fIrewritten ,
+without any extra spaces.
+When
+.B qmail-send
+sees the address
+.IR recipient ,
+it replaces it with
+.IR rewritten .
+Both
+.I recipient
+and
+.I rewritten
+must include domain names.
+.B qmail-send
+handles
+.I recipientmap
+before
+.IR locals .
+.TP 5
+.I virtualdomains
+List of virtual domains, one per line.
+Each virtual domain has the form
+.IR domain\fB:\fIprepend ,
+without any extra spaces.
+When
+.B qmail-send
+sees a recipient address at
+.IR domain ,
+say
+.IR user\fB@\fIdomain ,
+it converts it to
+.I prepend\fB-\fIuser\fB@\fIdomain
+and treats it as local.
+For example, if
+
+.EX
+     nowhere.mil:joeBREAKfoo
+.EE
+
+is in
+.IR virtualdomains ,
+and a message arrives for
+.BR info@nowhere.mil ,
+.B qmail-send
+will rewrite the recipient address as
+.B joeBREAKfoo-info@nowhere.mil
+and deliver the message locally.
+.I virtualdomains
+may contain wildcards:
+
+.EX
+     .fax:uucpBREAKfax
+     :aliasBREAKcatchall
+     .nowhere.mil:joeBREAKfoo-host
+.EE
+
+.I virtualdomains
+may also contain exceptions:
+an empty
+.I prepend
+means that
+.I domain
+is not a virtual domain.
+
+.B qmail-send
+handles
+.I virtualdomains
+after
+.IR locals :
+if a domain is listed in
+.IR locals ,
+.I virtualdomains
+does not apply.
+.SH "SEE ALSO"
+nice(1),
+addresses(5),
+envelopes(5),
+qmail-control(5),
+qmail-log(5),
+qmail-queue(8),
+qmail-clean(8),
+qmail-lspawn(8),
+qmail-rspawn(8)
diff --git a/qmail-send.c b/qmail-send.c
new file mode 100644 (file)
index 0000000..5f1d1cd
--- /dev/null
@@ -0,0 +1,1652 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "readwrite.h"
+#include "sig.h"
+#include "direntry.h"
+#include "control.h"
+#include "select.h"
+#include "open.h"
+#include "seek.h"
+#include "exit.h"
+#include "lock.h"
+#include "ndelay.h"
+#include "now.h"
+#include "getln.h"
+#include "substdio.h"
+#include "alloc.h"
+#include "error.h"
+#include "stralloc.h"
+#include "str.h"
+#include "byte.h"
+#include "fmt.h"
+#include "scan.h"
+#include "case.h"
+#include "auto_qmail.h"
+#include "trigger.h"
+#include "newfield.h"
+#include "quote.h"
+#include "qmail.h"
+#include "qsutil.h"
+#include "prioq.h"
+#include "constmap.h"
+#include "fmtqfn.h"
+#include "readsubdir.h"
+
+/* critical timing feature #1: if not triggered, do not busy-loop */
+/* critical timing feature #2: if triggered, respond within fixed time */
+/* important timing feature: when triggered, respond instantly */
+#define SLEEP_TODO 1500 /* check todo/ every 25 minutes in any case */
+#define SLEEP_FUZZ 1 /* slop a bit on sleeps to avoid zeno effect */
+#define SLEEP_FOREVER 86400 /* absolute maximum time spent in select() */
+#define SLEEP_CLEANUP 76431 /* time between cleanups */
+#define SLEEP_SYSFAIL 123
+#define OSSIFIED 129600 /* 36 hours; _must_ exceed q-q's DEATH (24 hours) */
+
+int lifetime = 604800;
+
+stralloc percenthack = {0};
+struct constmap mappercenthack;
+stralloc locals = {0};
+struct constmap maplocals;
+stralloc redir = {0};
+struct constmap mapredir;
+stralloc vdoms = {0};
+struct constmap mapvdoms;
+stralloc envnoathost = {0};
+stralloc bouncefrom = {0};
+stralloc bouncehost = {0};
+stralloc doublebounceto = {0};
+stralloc doublebouncehost = {0};
+
+char strnum2[FMT_ULONG];
+char strnum3[FMT_ULONG];
+
+#define CHANNELS 2
+char *chanaddr[CHANNELS] = { "local/", "remote/" };
+char *channodelmsg[CHANNELS] = {
+  "local deliveries will be put on hold\n"
+, "remote deliveries will be put on hold\n"
+};
+char *tochan[CHANNELS] = { " to local ", " to remote " };
+int chanfdout[CHANNELS] = { 1, 3 };
+int chanfdin[CHANNELS] = { 2, 4 };
+int chanskip[CHANNELS] = { 10, 20 };
+
+int flagexitasap = 0; void sigterm() { flagexitasap = 1; }
+int flagrunasap = 0; void sigalrm() { flagrunasap = 1; }
+int flagreadasap = 0; void sighup() { flagreadasap = 1; }
+
+void cleandied() { log1("alert: oh no! lost qmail-clean connection! dying...\n");
+ flagexitasap = 1; }
+
+int flagspawnalive[CHANNELS];
+void spawndied(c) int c; { log1("alert: oh no! lost spawn connection! dying...\n");
+ flagspawnalive[c] = 0; flagexitasap = 1; }
+
+#define REPORTMAX 10000
+
+datetime_sec recent;
+
+
+/* this file is too long ----------------------------------------- FILENAMES */
+
+stralloc fn = {0};
+stralloc fn2 = {0};
+char fnmake_strnum[FMT_ULONG];
+
+void fnmake_init()
+{
+ while (!stralloc_ready(&fn,FMTQFN)) nomem();
+ while (!stralloc_ready(&fn2,FMTQFN)) nomem();
+}
+
+void fnmake_info(id) unsigned long id; { fn.len = fmtqfn(fn.s,"info/",id,1); }
+void fnmake_todo(id) unsigned long id; { fn.len = fmtqfn(fn.s,"todo/",id,0); }
+void fnmake_mess(id) unsigned long id; { fn.len = fmtqfn(fn.s,"mess/",id,1); }
+void fnmake_foop(id) unsigned long id; { fn.len = fmtqfn(fn.s,"foop/",id,0); }
+void fnmake_split(id) unsigned long id; { fn.len = fmtqfn(fn.s,"",id,1); }
+void fnmake2_bounce(id) unsigned long id;
+{ fn2.len = fmtqfn(fn2.s,"bounce/",id,0); }
+void fnmake_chanaddr(id,c) unsigned long id; int c;
+{ fn.len = fmtqfn(fn.s,chanaddr[c],id,1); }
+
+
+/* this file is too long ----------------------------------------- REWRITING */
+
+stralloc rwline = {0};
+
+/* 1 if by land, 2 if by sea, 0 if out of memory. not allowed to barf. */
+/* may trash recip. must set up rwline, between a T and a \0. */
+int rewrite(recip)
+char *recip;
+{
+  int i;
+  int j;
+  char *x;
+  static stralloc addr = {0};
+  static stralloc domain = {0};
+
+  if (!stralloc_copys(&rwline,"T")) return 0;
+  if (!stralloc_copys(&addr,recip)) return 0;
+
+  i = byte_rchr(addr.s,addr.len,'@');
+  if (i == addr.len) {
+    if (!stralloc_cats(&addr,"@")) return 0;
+    if (!stralloc_cat(&addr,&envnoathost)) return 0;
+  }
+
+  while (constmap(&mappercenthack,addr.s + i + 1,addr.len - i - 1)) {
+    j = byte_rchr(addr.s,i,'%');
+    if (j == i) break;
+    addr.len = i;
+    i = j;
+    addr.s[i] = '@';
+  }
+
+  if (x = constmap(&mapredir,addr.s,addr.len))
+    if (x[str_chr(x,'@')])
+      if (!stralloc_copys(&addr,x)) return 0;
+
+  i = byte_rchr(addr.s,addr.len,'@');
+  if (!stralloc_copyb(&domain,addr.s + i + 1,addr.len - i - 1)) return 0;
+  addr.len = i;
+
+  if (constmap(&maplocals,domain.s,domain.len)) {
+    if (!stralloc_cat(&rwline,&addr)) return 0;
+    if (!stralloc_cats(&rwline,"@")) return 0;
+    if (!stralloc_cat(&rwline,&domain)) return 0;
+    if (!stralloc_0(&rwline)) return 0;
+    return 1;
+  }
+
+  for (i = 0;i <= domain.len;++i)
+    if ((i == 0) || (i == domain.len) || (domain.s[i] == '.'))
+      if (x = constmap(&mapvdoms,domain.s + i,domain.len - i)) {
+        if (!*x) break;
+        if (!stralloc_cats(&rwline,x)) return 0;
+        if (!stralloc_cats(&rwline,"-")) return 0;
+        if (!stralloc_cat(&rwline,&addr)) return 0;
+        if (!stralloc_cats(&rwline,"@")) return 0;
+        if (!stralloc_cat(&rwline,&domain)) return 0;
+        if (!stralloc_0(&rwline)) return 0;
+        return 1;
+      }
+  if (!stralloc_cat(&rwline,&addr)) return 0;
+  if (!stralloc_cats(&rwline,"@")) return 0;
+  if (!stralloc_cat(&rwline,&domain)) return 0;
+  if (!stralloc_0(&rwline)) return 0;
+  return 2;
+}
+
+void senderadd(sa,sender,recip)
+stralloc *sa;
+char *sender;
+char *recip;
+{
+ int i;
+ int j;
+ int k;
+
+ i = str_len(sender);
+ if (i >= 4)
+   if (str_equal(sender + i - 4,"-@[]"))
+    {
+     j = byte_rchr(sender,i - 4,'@');
+     k = str_rchr(recip,'@');
+     if (recip[k] && (j + 5 <= i))
+      {
+       /* owner-@host-@[] -> owner-recipbox=reciphost@host */
+       while (!stralloc_catb(sa,sender,j)) nomem();
+       while (!stralloc_catb(sa,recip,k)) nomem();
+       while (!stralloc_cats(sa,"=")) nomem();
+       while (!stralloc_cats(sa,recip + k + 1)) nomem();
+       while (!stralloc_cats(sa,"@")) nomem();
+       while (!stralloc_catb(sa,sender + j + 1,i - 5 - j)) nomem();
+       return;
+      }
+    }
+ while (!stralloc_cats(sa,sender)) nomem();
+}
+
+
+/* this file is too long ---------------------------------------------- INFO */
+
+int getinfo(sa,dt,id)
+stralloc *sa;
+datetime_sec *dt;
+unsigned long id;
+{
+ int fdinfo;
+ struct stat st;
+ static stralloc line = {0};
+ int match;
+ substdio ss;
+ char buf[128];
+
+ fnmake_info(id);
+ fdinfo = open_read(fn.s);
+ if (fdinfo == -1) return 0;
+ if (fstat(fdinfo,&st) == -1) { close(fdinfo); return 0; }
+ substdio_fdbuf(&ss,read,fdinfo,buf,sizeof(buf));
+ if (getln(&ss,&line,&match,'\0') == -1) { close(fdinfo); return 0; }
+ close(fdinfo);
+ if (!match) return 0;
+ if (line.s[0] != 'F') return 0;
+
+ *dt = st.st_mtime;
+ while (!stralloc_copys(sa,line.s + 1)) nomem();
+ while (!stralloc_0(sa)) nomem();
+ return 1;
+}
+
+
+/* this file is too long ------------------------------------- COMMUNICATION */
+
+substdio sstoqc; char sstoqcbuf[1024];
+substdio ssfromqc; char ssfromqcbuf[1024];
+stralloc comm_buf[CHANNELS] = { {0}, {0} };
+int comm_pos[CHANNELS];
+
+void comm_init()
+{
+ int c;
+ substdio_fdbuf(&sstoqc,write,5,sstoqcbuf,sizeof(sstoqcbuf));
+ substdio_fdbuf(&ssfromqc,read,6,ssfromqcbuf,sizeof(ssfromqcbuf));
+ for (c = 0;c < CHANNELS;++c)
+   if (ndelay_on(chanfdout[c]) == -1)
+   /* this is so stupid: NDELAY semantics should be default on write */
+     spawndied(c); /* drastic, but better than risking deadlock */
+}
+
+int comm_canwrite(c)
+int c;
+{
+ /* XXX: could allow a bigger buffer; say 10 recipients */
+ if (comm_buf[c].s && comm_buf[c].len) return 0;
+ return 1;
+}
+
+void comm_write(c,delnum,id,sender,recip)
+int c;
+int delnum;
+unsigned long id;
+char *sender;
+char *recip;
+{
+ char ch;
+ if (comm_buf[c].s && comm_buf[c].len) return;
+ while (!stralloc_copys(&comm_buf[c],"")) nomem();
+ ch = delnum;
+ while (!stralloc_append(&comm_buf[c],&ch)) nomem();
+ fnmake_split(id);
+ while (!stralloc_cats(&comm_buf[c],fn.s)) nomem();
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ senderadd(&comm_buf[c],sender,recip);
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ while (!stralloc_cats(&comm_buf[c],recip)) nomem();
+ while (!stralloc_0(&comm_buf[c])) nomem();
+ comm_pos[c] = 0;
+}
+
+void comm_selprep(nfds,wfds)
+int *nfds;
+fd_set *wfds;
+{
+ int c;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c])
+     if (comm_buf[c].s && comm_buf[c].len)
+      {
+       FD_SET(chanfdout[c],wfds);
+       if (*nfds <= chanfdout[c])
+         *nfds = chanfdout[c] + 1;
+      }
+}
+
+void comm_do(wfds)
+fd_set *wfds;
+{
+ int c;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c])
+     if (comm_buf[c].s && comm_buf[c].len)
+       if (FD_ISSET(chanfdout[c],wfds))
+        {
+         int w;
+         int len;
+         len = comm_buf[c].len;
+         w = write(chanfdout[c],comm_buf[c].s + comm_pos[c],len - comm_pos[c]);
+         if (w <= 0)
+         {
+          if ((w == -1) && (errno == error_pipe))
+            spawndied(c);
+          else
+            continue; /* kernel select() bug; can't avoid busy-looping */
+         }
+        else
+         {
+          comm_pos[c] += w;
+          if (comm_pos[c] == len)
+            comm_buf[c].len = 0;
+         }
+        }
+}
+
+
+/* this file is too long ------------------------------------------ CLEANUPS */
+
+int flagcleanup; /* if 1, cleanupdir is initialized and ready */
+readsubdir cleanupdir;
+datetime_sec cleanuptime;
+
+void cleanup_init()
+{
+ flagcleanup = 0;
+ cleanuptime = now();
+}
+
+void cleanup_selprep(wakeup)
+datetime_sec *wakeup;
+{
+ if (flagcleanup) *wakeup = 0;
+ if (*wakeup > cleanuptime) *wakeup = cleanuptime;
+}
+
+void cleanup_do()
+{
+ char ch;
+ struct stat st;
+ unsigned long id;
+
+ if (!flagcleanup)
+  {
+   if (recent < cleanuptime) return;
+   readsubdir_init(&cleanupdir,"mess",pausedir);
+   flagcleanup = 1;
+  }
+
+ switch(readsubdir_next(&cleanupdir,&id))
+  {
+   case 1:
+     break;
+   case 0:
+     flagcleanup = 0;
+     cleanuptime = recent + SLEEP_CLEANUP;
+   default:
+     return;
+  }
+
+ fnmake_mess(id);
+ if (stat(fn.s,&st) == -1) return; /* probably qmail-queue deleted it */
+ if (recent <= st.st_atime + OSSIFIED) return;
+
+ fnmake_info(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != error_noent) return;
+ fnmake_todo(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != error_noent) return;
+
+ fnmake_foop(id);
+ if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+')
+   log3("warning: qmail-clean unable to clean up ",fn.s,"\n");
+}
+
+
+/* this file is too long ----------------------------------- PRIORITY QUEUES */
+
+prioq pqdone = {0}; /* -todo +info; HOPEFULLY -local -remote */
+prioq pqchan[CHANNELS] = { {0}, {0} };
+/* pqchan 0: -todo +info +local ?remote */
+/* pqchan 1: -todo +info ?local +remote */
+prioq pqfail = {0}; /* stat() failure; has to be pqadded again */
+
+void pqadd(id)
+unsigned long id;
+{
+ struct prioq_elt pe;
+ struct prioq_elt pechan[CHANNELS];
+ int flagchan[CHANNELS];
+ struct stat st;
+ int c;
+
+#define CHECKSTAT if (errno != error_noent) goto fail;
+
+ fnmake_info(id);
+ if (stat(fn.s,&st) == -1)
+  {
+   CHECKSTAT
+   return; /* someone yanking our chain */
+  }
+
+ fnmake_todo(id);
+ if (stat(fn.s,&st) != -1) return; /* look, ma, dad crashed writing info! */
+ CHECKSTAT
+
+ for (c = 0;c < CHANNELS;++c)
+  {
+   fnmake_chanaddr(id,c);
+   if (stat(fn.s,&st) == -1) { flagchan[c] = 0; CHECKSTAT }
+   else { flagchan[c] = 1; pechan[c].id = id; pechan[c].dt = st.st_mtime; }
+  }
+
+ for (c = 0;c < CHANNELS;++c)
+   if (flagchan[c])
+     while (!prioq_insert(&pqchan[c],&pechan[c])) nomem();
+
+ for (c = 0;c < CHANNELS;++c) if (flagchan[c]) break;
+ if (c == CHANNELS)
+  {
+   pe.id = id; pe.dt = now();
+   while (!prioq_insert(&pqdone,&pe)) nomem();
+  }
+
+ return;
+
+ fail:
+ log3("warning: unable to stat ",fn.s,"; will try again later\n");
+ pe.id = id; pe.dt = now() + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqfail,&pe)) nomem();
+}
+
+void pqstart()
+{
+ readsubdir rs;
+ int x;
+ unsigned long id;
+
+ readsubdir_init(&rs,"info",pausedir);
+
+ while (x = readsubdir_next(&rs,&id))
+   if (x > 0)
+     pqadd(id);
+}
+
+void pqfinish()
+{
+ int c;
+ struct prioq_elt pe;
+ time_t ut[2]; /* XXX: more portable than utimbuf, but still worrisome */
+
+ for (c = 0;c < CHANNELS;++c)
+   while (prioq_min(&pqchan[c],&pe))
+    {
+     prioq_delmin(&pqchan[c]);
+     fnmake_chanaddr(pe.id,c);
+     ut[0] = ut[1] = pe.dt;
+     if (utime(fn.s,ut) == -1)
+       log3("warning: unable to utime ",fn.s,"; message will be retried too soon\n");
+    }
+}
+
+void pqrun()
+{
+ int c;
+ int i;
+ for (c = 0;c < CHANNELS;++c)
+   if (pqchan[c].p)
+     if (pqchan[c].len)
+       for (i = 0;i < pqchan[c].len;++i)
+        pqchan[c].p[i].dt = recent;
+}
+
+
+/* this file is too long ---------------------------------------------- JOBS */
+
+struct job
+ {
+  int refs; /* if 0, this struct is unused */
+  unsigned long id;
+  int channel;
+  datetime_sec retry;
+  stralloc sender;
+  int numtodo;
+  int flaghiteof;
+  int flagdying;
+ }
+;
+
+int numjobs;
+struct job *jo;
+
+void job_init()
+{
+ int j;
+ while (!(jo = (struct job *) alloc(numjobs * sizeof(struct job)))) nomem();
+ for (j = 0;j < numjobs;++j)
+  {
+   jo[j].refs = 0;
+   jo[j].sender.s = 0;
+  }
+}
+
+int job_avail()
+{
+ int j;
+ for (j = 0;j < numjobs;++j) if (!jo[j].refs) return 1;
+ return 0;
+}
+
+int job_open(id,channel)
+unsigned long id;
+int channel;
+{
+ int j;
+ for (j = 0;j < numjobs;++j) if (!jo[j].refs) break;
+ if (j == numjobs) return -1;
+ jo[j].refs = 1;
+ jo[j].id = id;
+ jo[j].channel = channel;
+ jo[j].numtodo = 0;
+ jo[j].flaghiteof = 0;
+ return j;
+}
+
+void job_close(j)
+int j;
+{
+ struct prioq_elt pe;
+ struct stat st;
+
+ if (0 < --jo[j].refs) return;
+
+ pe.id = jo[j].id;
+ pe.dt = jo[j].retry;
+ if (jo[j].flaghiteof && !jo[j].numtodo)
+  {
+   fnmake_chanaddr(jo[j].id,jo[j].channel);
+   if (unlink(fn.s) == -1)
+    {
+     log3("warning: unable to unlink ",fn.s,"; will try again later\n");
+     pe.dt = now() + SLEEP_SYSFAIL;
+    }
+   else
+    {
+     int c;
+     for (c = 0;c < CHANNELS;++c) if (c != jo[j].channel)
+      {
+       fnmake_chanaddr(jo[j].id,c);
+       if (stat(fn.s,&st) == 0) return; /* more channels going */
+       if (errno != error_noent)
+       {
+         log3("warning: unable to stat ",fn.s,"\n");
+        break; /* this is the only reason for HOPEFULLY */
+       }
+      }
+     pe.dt = now();
+     while (!prioq_insert(&pqdone,&pe)) nomem();
+     return;
+    }
+  }
+
+ while (!prioq_insert(&pqchan[jo[j].channel],&pe)) nomem();
+}
+
+
+/* this file is too long ------------------------------------------- BOUNCES */
+
+char *stripvdomprepend(recip)
+char *recip;
+{
+ int i;
+ char *domain;
+ int domainlen;
+ char *prepend;
+
+ i = str_rchr(recip,'@');
+ if (!recip[i]) return recip;
+ domain = recip + i + 1;
+ domainlen = str_len(domain);
+
+ for (i = 0;i <= domainlen;++i)
+   if ((i == 0) || (i == domainlen) || (domain[i] == '.'))
+     if (prepend = constmap(&mapvdoms,domain + i,domainlen - i))
+      {
+       if (!*prepend) break;
+       i = str_len(prepend);
+       if (str_diffn(recip,prepend,i)) break;
+       if (recip[i] != '-') break;
+       return recip + i + 1;
+      }
+ return recip;
+}
+
+stralloc bouncetext = {0};
+
+void addbounce(id,recip,report)
+unsigned long id;
+char *recip;
+char *report;
+{
+ int fd;
+ int pos;
+ int w;
+ while (!stralloc_copys(&bouncetext,"<")) nomem();
+ while (!stralloc_cats(&bouncetext,stripvdomprepend(recip))) nomem();
+ for (pos = 0;pos < bouncetext.len;++pos)
+   if (bouncetext.s[pos] == '\n')
+     bouncetext.s[pos] = '_';
+ while (!stralloc_cats(&bouncetext,">:\n")) nomem();
+ while (!stralloc_cats(&bouncetext,report)) nomem();
+ if (report[0])
+   if (report[str_len(report) - 1] != '\n')
+     while (!stralloc_cats(&bouncetext,"\n")) nomem();
+ for (pos = bouncetext.len - 2;pos > 0;--pos)
+   if (bouncetext.s[pos] == '\n')
+     if (bouncetext.s[pos - 1] == '\n')
+       bouncetext.s[pos] = '/';
+ while (!stralloc_cats(&bouncetext,"\n")) nomem();
+ fnmake2_bounce(id);
+ for (;;)
+  {
+   fd = open_append(fn2.s);
+   if (fd != -1) break;
+   log1("alert: unable to append to bounce message; HELP! sleeping...\n");
+   sleep(10);
+  }
+ pos = 0;
+ while (pos < bouncetext.len)
+  {
+   w = write(fd,bouncetext.s + pos,bouncetext.len - pos);
+   if (w <= 0)
+    {
+     log1("alert: unable to append to bounce message; HELP! sleeping...\n");
+     sleep(10);
+    }
+   else
+     pos += w;
+  }
+ close(fd);
+}
+
+int injectbounce(id)
+unsigned long id;
+{
+ struct qmail qqt;
+ struct stat st;
+ char *bouncesender;
+ char *bouncerecip;
+ int r;
+ int fd;
+ substdio ssread;
+ char buf[128];
+ char inbuf[128];
+ static stralloc sender = {0};
+ static stralloc quoted = {0};
+ datetime_sec birth;
+ unsigned long qp;
+
+ if (!getinfo(&sender,&birth,id)) return 0; /* XXX: print warning */
+
+ /* owner-@host-@[] -> owner-@host */
+ if (sender.len >= 5)
+   if (str_equal(sender.s + sender.len - 5,"-@[]"))
+    {
+     sender.len -= 4;
+     sender.s[sender.len - 1] = 0;
+    }
+
+ fnmake2_bounce(id);
+ fnmake_mess(id);
+ if (stat(fn2.s,&st) == -1)
+  {
+   if (errno == error_noent)
+     return 1;
+   log3("warning: unable to stat ",fn2.s,"\n");
+   return 0;
+  }
+ if (str_equal(sender.s,"#@[]"))
+   log3("triple bounce: discarding ",fn2.s,"\n");
+ else
+  {
+   if (qmail_open(&qqt) == -1)
+    { log1("warning: unable to start qmail-queue, will try later\n"); return 0; }
+   qp = qmail_qp(&qqt);
+
+   if (*sender.s) { bouncesender = ""; bouncerecip = sender.s; }
+   else { bouncesender = "#@[]"; bouncerecip = doublebounceto.s; }
+
+   while (!newfield_datemake(now())) nomem();
+   qmail_put(&qqt,newfield_date.s,newfield_date.len);
+   qmail_puts(&qqt,"From: ");
+   while (!quote(&quoted,&bouncefrom)) nomem();
+   qmail_put(&qqt,quoted.s,quoted.len);
+   qmail_puts(&qqt,"@");
+   qmail_put(&qqt,bouncehost.s,bouncehost.len);
+   qmail_puts(&qqt,"\nTo: ");
+   while (!quote2(&quoted,bouncerecip)) nomem();
+   qmail_put(&qqt,quoted.s,quoted.len);
+   qmail_puts(&qqt,"\n\
+Subject: failure notice\n\
+\n\
+Hi. This is the qmail-send program at ");
+   qmail_put(&qqt,bouncehost.s,bouncehost.len);
+   qmail_puts(&qqt,*sender.s ? ".\n\
+I'm afraid I wasn't able to deliver your message to the following addresses.\n\
+This is a permanent error; I've given up. Sorry it didn't work out.\n\
+\n\
+" : ".\n\
+I tried to deliver a bounce message to this address, but the bounce bounced!\n\
+\n\
+");
+
+   fd = open_read(fn2.s);
+   if (fd == -1)
+     qmail_fail(&qqt);
+   else
+    {
+     substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf));
+     while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0)
+       qmail_put(&qqt,buf,r);
+     close(fd);
+     if (r == -1)
+       qmail_fail(&qqt);
+    }
+
+   qmail_puts(&qqt,*sender.s ? "--- Below this line is a copy of the message.\n\n" : "--- Below this line is the original bounce.\n\n");
+   qmail_puts(&qqt,"Return-Path: <");
+   while (!quote2(&quoted,sender.s)) nomem();
+   qmail_put(&qqt,quoted.s,quoted.len);
+   qmail_puts(&qqt,">\n");
+
+   fd = open_read(fn.s);
+   if (fd == -1)
+     qmail_fail(&qqt);
+   else
+    {
+     substdio_fdbuf(&ssread,read,fd,inbuf,sizeof(inbuf));
+     while ((r = substdio_get(&ssread,buf,sizeof(buf))) > 0)
+       qmail_put(&qqt,buf,r);
+     close(fd);
+     if (r == -1)
+       qmail_fail(&qqt);
+    }
+
+   qmail_from(&qqt,bouncesender);
+   qmail_to(&qqt,bouncerecip);
+   if (qmail_close(&qqt))
+    { log1("warning: trouble injecting bounce message, will try later\n"); return 0; }
+
+   strnum2[fmt_ulong(strnum2,id)] = 0;
+   log2("bounce msg ",strnum2);
+   strnum2[fmt_ulong(strnum2,qp)] = 0;
+   log3(" qp ",strnum2,"\n");
+  }
+ if (unlink(fn2.s) == -1)
+  {
+   log3("warning: unable to unlink ",fn2.s,"\n");
+   return 0;
+  }
+ return 1;
+}
+
+
+/* this file is too long ---------------------------------------- DELIVERIES */
+
+struct del
+ {
+  int used;
+  int j;
+  unsigned long delid;
+  seek_pos mpos;
+  stralloc recip;
+ }
+;
+
+unsigned long masterdelid = 1;
+unsigned int concurrency[CHANNELS] = { 10, 20 };
+struct del *d[CHANNELS];
+stralloc dline[CHANNELS];
+char delbuf[2048];
+
+void del_init()
+{
+ int c;
+ int i;
+ for (c = 0;c < CHANNELS;++c)
+  {
+   flagspawnalive[c] = 1;
+   while (!(d[c] = (struct del *) alloc(concurrency[c] * sizeof(struct del))))
+     nomem();
+   for (i = 0;i < concurrency[c];++i)
+    { d[c][i].used = 0; d[c][i].recip.s = 0; }
+   dline[c].s = 0;
+   while (!stralloc_copys(&dline[c],"")) nomem();
+  }
+}
+
+int del_canexit()
+{
+ int i;
+ int c;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c]) /* if dead, nothing we can do about its jobs */
+     for (i = 0;i < concurrency[c];++i)
+       if (d[c][i].used) return 0;
+ return 1;
+}
+
+static int del_lastsaid = 0;
+
+void del_saywhynoexit()
+{
+ int i;
+ int c;
+ int n;
+ n = 0;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c])
+     for (i = 0;i < concurrency[c];++i)
+       if (d[c][i].used)
+        ++n;
+ if (!del_lastsaid || (n < del_lastsaid))
+  {
+   strnum2[fmt_ulong(strnum2,(unsigned long) n)] = 0;
+   log3("number of deliveries left before exiting: ",strnum2,"\n");
+   del_lastsaid = n;
+  }
+}
+
+int del_avail(c)
+int c;
+{
+ int i;
+
+ if (!flagspawnalive[c]) return 0;
+ if (!comm_canwrite(c)) return 0;
+ for (i = 0;i < concurrency[c];++i) if (!d[c][i].used) return 1;
+ return 0;
+}
+
+void del_start(j,mpos,recip)
+int j;
+seek_pos mpos;
+char *recip;
+{
+ int i;
+ int c;
+
+ c = jo[j].channel;
+ if (!flagspawnalive[c]) return;
+ if (!comm_canwrite(c)) return;
+
+ for (i = 0;i < concurrency[c];++i) if (!d[c][i].used) break;
+ if (i == concurrency[c]) return;
+
+ if (!stralloc_copys(&d[c][i].recip,recip)) { nomem(); return; }
+ if (!stralloc_0(&d[c][i].recip)) { nomem(); return; }
+ d[c][i].j = j; ++jo[j].refs;
+ d[c][i].delid = masterdelid++;
+ d[c][i].mpos = mpos;
+ d[c][i].used = 1;
+
+ comm_write(c,i,jo[j].id,jo[j].sender.s,recip);
+
+ strnum2[fmt_ulong(strnum2,d[c][i].delid)] = 0;
+ strnum3[fmt_ulong(strnum3,jo[j].id)] = 0;
+ log2("starting delivery ",strnum2);
+ log3(": msg ",strnum3,tochan[c]);
+ logsafe(recip);
+ log1("\n");
+}
+
+void markdone(c,id,pos)
+int c;
+unsigned long id;
+seek_pos pos;
+{
+ struct stat st;
+ int fd;
+ fnmake_chanaddr(id,c);
+ for (;;)
+  {
+   fd = open_write(fn.s);
+   if (fd == -1) break;
+   if (fstat(fd,&st) == -1) { close(fd); break; }
+   if (seek_set(fd,pos) == -1) { close(fd); break; }
+   if (write(fd,"D",1) != 1) { close(fd); break; }
+   /* further errors -> double delivery without us knowing about it, oh well */
+   close(fd);
+   return;
+  }
+ log3("warning: trouble marking ",fn.s,"; message will be delivered twice!\n");
+}
+
+void del_dochan(c)
+int c;
+{
+ int r;
+ char ch;
+ int i;
+ int delnum;
+ r = read(chanfdin[c],delbuf,sizeof(delbuf));
+ if (r == -1) return;
+ if (r == 0) { spawndied(c); return; }
+ for (i = 0;i < r;++i)
+  {
+   ch = delbuf[i];
+   while (!stralloc_append(&dline[c],&ch)) nomem();
+   if (dline[c].len > REPORTMAX)
+     dline[c].len = REPORTMAX;
+     /* qmail-lspawn and qmail-rspawn are responsible for keeping it short */
+     /* but from a security point of view, we don't trust rspawn */
+   if (!ch && (dline[c].len > 1))
+    {
+     delnum = (unsigned int) (unsigned char) dline[c].s[0];
+     if ((delnum < 0) || (delnum >= concurrency[c]) || !d[c][delnum].used)
+       log1("warning: internal error: delivery report out of range\n");
+     else
+      {
+       strnum3[fmt_ulong(strnum3,d[c][delnum].delid)] = 0;
+       if (dline[c].s[1] == 'Z')
+        if (jo[d[c][delnum].j].flagdying)
+         {
+          dline[c].s[1] = 'D';
+          --dline[c].len;
+          while (!stralloc_cats(&dline[c],"I'm not going to try again; this message has been in the queue too long.\n")) nomem();
+          while (!stralloc_0(&dline[c])) nomem();
+         }
+       switch(dline[c].s[1])
+       {
+        case 'K':
+          log3("delivery ",strnum3,": success: ");
+          logsafe(dline[c].s + 2);
+          log1("\n");
+          markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
+          --jo[d[c][delnum].j].numtodo;
+          break;
+        case 'Z':
+          log3("delivery ",strnum3,": deferral: ");
+          logsafe(dline[c].s + 2);
+          log1("\n");
+          break;
+        case 'D':
+          log3("delivery ",strnum3,": failure: ");
+          logsafe(dline[c].s + 2);
+          log1("\n");
+          addbounce(jo[d[c][delnum].j].id,d[c][delnum].recip.s,dline[c].s + 2);
+          markdone(c,jo[d[c][delnum].j].id,d[c][delnum].mpos);
+          --jo[d[c][delnum].j].numtodo;
+          break;
+        default:
+          log3("delivery ",strnum3,": report mangled, will defer\n");
+       }
+       job_close(d[c][delnum].j);
+       d[c][delnum].used = 0;
+      }
+     dline[c].len = 0;
+    }
+  }
+}
+
+void del_selprep(nfds,rfds)
+int *nfds;
+fd_set *rfds;
+{
+ int c;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c])
+    {
+     FD_SET(chanfdin[c],rfds);
+     if (*nfds <= chanfdin[c])
+       *nfds = chanfdin[c] + 1;
+    }
+}
+
+void del_do(rfds)
+fd_set *rfds;
+{
+ int c;
+ for (c = 0;c < CHANNELS;++c)
+   if (flagspawnalive[c])
+     if (FD_ISSET(chanfdin[c],rfds))
+       del_dochan(c);
+}
+
+
+/* this file is too long -------------------------------------------- PASSES */
+
+struct
+ {
+  unsigned long id; /* if 0, need a new pass */
+  int j; /* defined if id; job number */
+  int fd; /* defined if id; reading from {local,remote} */
+  seek_pos mpos; /* defined if id; mark position */
+  substdio ss;
+  char buf[128];
+ }
+pass[CHANNELS];
+
+void pass_init()
+{
+ int c;
+ for (c = 0;c < CHANNELS;++c) pass[c].id = 0;
+}
+
+void pass_selprep(wakeup)
+datetime_sec *wakeup;
+{
+ int c;
+ struct prioq_elt pe;
+ if (flagexitasap) return;
+ for (c = 0;c < CHANNELS;++c)
+   if (pass[c].id)
+     if (del_avail(c))
+      { *wakeup = 0; return; }
+ if (job_avail())
+   for (c = 0;c < CHANNELS;++c)
+     if (!pass[c].id)
+       if (prioq_min(&pqchan[c],&pe))
+         if (*wakeup > pe.dt)
+           *wakeup = pe.dt;
+ if (prioq_min(&pqfail,&pe))
+   if (*wakeup > pe.dt)
+     *wakeup = pe.dt;
+ if (prioq_min(&pqdone,&pe))
+   if (*wakeup > pe.dt)
+     *wakeup = pe.dt;
+}
+
+static datetime_sec squareroot(x) /* result^2 <= x < (result + 1)^2 */
+datetime_sec x; /* assuming: >= 0 */
+{
+ datetime_sec y;
+ datetime_sec yy;
+ datetime_sec y21;
+ int j;
+
+ y = 0; yy = 0;
+ for (j = 15;j >= 0;--j)
+  {
+   y21 = (y << (j + 1)) + (1 << (j + j));
+   if (y21 <= x - yy) { y += (1 << j); yy += y21; }
+  }
+ return y;
+}
+
+datetime_sec nextretry(birth,c)
+datetime_sec birth;
+int c;
+{
+ int n;
+
+ if (birth > recent) n = 0;
+ else n = squareroot(recent - birth); /* no need to add fuzz to recent */
+ n += chanskip[c];
+ return birth + n * n;
+}
+
+void pass_dochan(c)
+int c;
+{
+ datetime_sec birth;
+ struct prioq_elt pe;
+ static stralloc line = {0};
+ int match;
+
+ if (flagexitasap) return;
+
+ if (!pass[c].id)
+  {
+   if (!job_avail()) return;
+   if (!prioq_min(&pqchan[c],&pe)) return;
+   if (pe.dt > recent) return;
+   fnmake_chanaddr(pe.id,c);
+
+   prioq_delmin(&pqchan[c]);
+   pass[c].mpos = 0;
+   pass[c].fd = open_read(fn.s);
+   if (pass[c].fd == -1) goto trouble;
+   if (!getinfo(&line,&birth,pe.id)) { close(pass[c].fd); goto trouble; }
+   pass[c].id = pe.id;
+   substdio_fdbuf(&pass[c].ss,read,pass[c].fd,pass[c].buf,sizeof(pass[c].buf));
+   pass[c].j = job_open(pe.id,c);
+   jo[pass[c].j].retry = nextretry(birth,c);
+   jo[pass[c].j].flagdying = (recent > birth + lifetime);
+   while (!stralloc_copy(&jo[pass[c].j].sender,&line)) nomem();
+  }
+
+ if (!del_avail(c)) return;
+
+ if (getln(&pass[c].ss,&line,&match,'\0') == -1)
+  {
+   fnmake_chanaddr(pass[c].id,c);
+   log3("warning: trouble reading ",fn.s,"; will try again later\n");
+   close(pass[c].fd);
+   job_close(pass[c].j);
+   pass[c].id = 0;
+   return;
+  }
+ if (!match)
+  {
+   close(pass[c].fd);
+   jo[pass[c].j].flaghiteof = 1;
+   job_close(pass[c].j);
+   pass[c].id = 0;
+   return;
+  }
+ switch(line.s[0])
+  {
+   case 'T':
+     ++jo[pass[c].j].numtodo;
+     del_start(pass[c].j,pass[c].mpos,line.s + 1);
+     break;
+   case 'D':
+     break;
+   default:
+     fnmake_chanaddr(pass[c].id,c);
+     log3("warning: unknown record type in ",fn.s,"!\n");
+     close(pass[c].fd);
+     job_close(pass[c].j);
+     pass[c].id = 0;
+     return;
+  }
+
+ pass[c].mpos += line.len;
+ return;
+
+ trouble:
+ log3("warning: trouble opening ",fn.s,"; will try again later\n");
+ pe.dt = recent + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqchan[c],&pe)) nomem();
+}
+
+void messdone(id)
+unsigned long id;
+{
+ char ch;
+ int c;
+ struct prioq_elt pe;
+ struct stat st;
+
+ for (c = 0;c < CHANNELS;++c)
+  {
+   fnmake_chanaddr(id,c);
+   if (stat(fn.s,&st) == 0) return; /* false alarm; consequence of HOPEFULLY */
+   if (errno != error_noent)
+    {
+     log3("warning: unable to stat ",fn.s,"; will try again later\n");
+     goto fail;
+    }
+  }
+
+ fnmake_todo(id);
+ if (stat(fn.s,&st) == 0) return;
+ if (errno != error_noent)
+  {
+   log3("warning: unable to stat ",fn.s,"; will try again later\n");
+   goto fail;
+  }
+ fnmake_info(id);
+ if (stat(fn.s,&st) == -1)
+  {
+   if (errno == error_noent) return;
+   log3("warning: unable to stat ",fn.s,"; will try again later\n");
+   goto fail;
+  }
+ /* -todo +info -local -remote ?bounce */
+ if (!injectbounce(id))
+   goto fail; /* injectbounce() produced error message */
+
+ strnum3[fmt_ulong(strnum3,id)] = 0;
+ log3("end msg ",strnum3,"\n");
+
+ /* -todo +info -local -remote -bounce */
+ fnmake_info(id);
+ if (unlink(fn.s) == -1)
+  {
+   log3("warning: unable to unlink ",fn.s,"; will try again later\n");
+   goto fail;
+  }
+
+ /* -todo -info -local -remote -bounce; we can relax */
+ fnmake_foop(id);
+ if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+')
+   log3("warning: qmail-clean unable to clean up ",fn.s,"\n");
+
+ return;
+
+ fail:
+ pe.id = id; pe.dt = now() + SLEEP_SYSFAIL;
+ while (!prioq_insert(&pqdone,&pe)) nomem();
+}
+
+void pass_do()
+{
+ int c;
+ struct prioq_elt pe;
+
+ for (c = 0;c < CHANNELS;++c) pass_dochan(c);
+ if (prioq_min(&pqfail,&pe))
+   if (pe.dt <= recent)
+    {
+     prioq_delmin(&pqfail);
+     pqadd(pe.id);
+    }
+ if (prioq_min(&pqdone,&pe))
+   if (pe.dt <= recent)
+    {
+     prioq_delmin(&pqdone);
+     messdone(pe.id);
+    }
+}
+
+
+/* this file is too long ---------------------------------------------- TODO */
+
+datetime_sec nexttodorun;
+DIR *tododir; /* if 0, have to opendir again */
+stralloc todoline = {0};
+char todobuf[SUBSTDIO_INSIZE];
+char todobufinfo[512];
+char todobufchan[CHANNELS][1024];
+
+void todo_init()
+{
+ tododir = 0;
+ nexttodorun = now();
+ trigger_set();
+}
+
+void todo_selprep(nfds,rfds,wakeup)
+int *nfds;
+fd_set *rfds;
+datetime_sec *wakeup;
+{
+ if (flagexitasap) return;
+ trigger_selprep(nfds,rfds);
+ if (tododir) *wakeup = 0;
+ if (*wakeup > nexttodorun) *wakeup = nexttodorun;
+}
+
+void todo_do(rfds)
+fd_set *rfds;
+{
+ struct stat st;
+ substdio ss; int fd;
+ substdio ssinfo; int fdinfo;
+ substdio sschan[CHANNELS];
+ int fdchan[CHANNELS];
+ int flagchan[CHANNELS];
+ struct prioq_elt pe;
+ char ch;
+ int match;
+ unsigned long id;
+ unsigned int len;
+ direntry *d;
+ int c;
+ unsigned long uid;
+ unsigned long pid;
+
+ fd = -1;
+ fdinfo = -1;
+ for (c = 0;c < CHANNELS;++c) fdchan[c] = -1;
+
+ if (flagexitasap) return;
+
+ if (!tododir)
+  {
+   if (!trigger_pulled(rfds))
+     if (recent < nexttodorun)
+       return;
+   trigger_set();
+   tododir = opendir("todo");
+   if (!tododir)
+    {
+     pausedir("todo");
+     return;
+    }
+   nexttodorun = recent + SLEEP_TODO;
+  }
+
+ d = readdir(tododir);
+ if (!d)
+  {
+   closedir(tododir);
+   tododir = 0;
+   return;
+  }
+ if (str_equal(d->d_name,".")) return;
+ if (str_equal(d->d_name,"..")) return;
+ len = scan_ulong(d->d_name,&id);
+ if (!len || d->d_name[len]) return;
+
+ fnmake_todo(id);
+
+ fd = open_read(fn.s);
+ if (fd == -1) { log3("warning: unable to open ",fn.s,"\n"); return; }
+
+ fnmake_mess(id);
+ /* just for the statistics */
+ if (stat(fn.s,&st) == -1)
+  { log3("warning: unable to stat ",fn.s,"\n"); goto fail; }
+
+ for (c = 0;c < CHANNELS;++c)
+  {
+   fnmake_chanaddr(id,c);
+   if (unlink(fn.s) == -1) if (errno != error_noent)
+    { log3("warning: unable to unlink ",fn.s,"\n"); goto fail; }
+  }
+
+ fnmake_info(id);
+ if (unlink(fn.s) == -1) if (errno != error_noent)
+  { log3("warning: unable to unlink ",fn.s,"\n"); goto fail; }
+
+ fdinfo = open_excl(fn.s);
+ if (fdinfo == -1)
+  { log3("warning: unable to create ",fn.s,"\n"); goto fail; }
+
+ strnum3[fmt_ulong(strnum3,id)] = 0;
+ log3("new msg ",strnum3,"\n");
+
+ for (c = 0;c < CHANNELS;++c) flagchan[c] = 0;
+
+ substdio_fdbuf(&ss,read,fd,todobuf,sizeof(todobuf));
+ substdio_fdbuf(&ssinfo,write,fdinfo,todobufinfo,sizeof(todobufinfo));
+
+ uid = 0;
+ pid = 0;
+
+ for (;;)
+  {
+   if (getln(&ss,&todoline,&match,'\0') == -1)
+    {
+     /* perhaps we're out of memory, perhaps an I/O error */
+     fnmake_todo(id);
+     log3("warning: trouble reading ",fn.s,"\n"); goto fail;
+    }
+   if (!match) break;
+
+   switch(todoline.s[0])
+    {
+     case 'u':
+       scan_ulong(todoline.s + 1,&uid);
+       break;
+     case 'p':
+       scan_ulong(todoline.s + 1,&pid);
+       break;
+     case 'F':
+       if (substdio_putflush(&ssinfo,todoline.s,todoline.len) == -1)
+       {
+        fnmake_info(id);
+         log3("warning: trouble writing to ",fn.s,"\n"); goto fail;
+       }
+       log2("info msg ",strnum3);
+       strnum2[fmt_ulong(strnum2,(unsigned long) st.st_size)] = 0;
+       log2(": bytes ",strnum2);
+       log1(" from <"); logsafe(todoline.s + 1);
+       strnum2[fmt_ulong(strnum2,pid)] = 0;
+       log2("> qp ",strnum2);
+       strnum2[fmt_ulong(strnum2,uid)] = 0;
+       log2(" uid ",strnum2);
+       log1("\n");
+       break;
+     case 'T':
+       switch(rewrite(todoline.s + 1))
+       {
+        case 0: nomem(); goto fail;
+        case 2: c = 1; break;
+        default: c = 0; break;
+        }
+       if (fdchan[c] == -1)
+       {
+        fnmake_chanaddr(id,c);
+        fdchan[c] = open_excl(fn.s);
+        if (fdchan[c] == -1)
+          { log3("warning: unable to create ",fn.s,"\n"); goto fail; }
+        substdio_fdbuf(&sschan[c]
+          ,write,fdchan[c],todobufchan[c],sizeof(todobufchan[c]));
+        flagchan[c] = 1;
+       }
+       if (substdio_bput(&sschan[c],rwline.s,rwline.len) == -1)
+        {
+        fnmake_chanaddr(id,c);
+         log3("warning: trouble writing to ",fn.s,"\n"); goto fail;
+        }
+       break;
+     default:
+       fnmake_todo(id);
+       log3("warning: unknown record type in ",fn.s,"\n"); goto fail;
+    }
+  }
+
+ close(fd); fd = -1;
+
+ fnmake_info(id);
+ if (substdio_flush(&ssinfo) == -1)
+  { log3("warning: trouble writing to ",fn.s,"\n"); goto fail; }
+ if (fsync(fdinfo) == -1)
+  { log3("warning: trouble fsyncing ",fn.s,"\n"); goto fail; }
+ close(fdinfo); fdinfo = -1;
+
+ for (c = 0;c < CHANNELS;++c)
+   if (fdchan[c] != -1)
+    {
+     fnmake_chanaddr(id,c);
+     if (substdio_flush(&sschan[c]) == -1)
+      { log3("warning: trouble writing to ",fn.s,"\n"); goto fail; }
+     if (fsync(fdchan[c]) == -1)
+      { log3("warning: trouble fsyncing ",fn.s,"\n"); goto fail; }
+     close(fdchan[c]); fdchan[c] = -1;
+    }
+
+ fnmake_todo(id);
+ if (substdio_putflush(&sstoqc,fn.s,fn.len) == -1) { cleandied(); return; }
+ if (substdio_get(&ssfromqc,&ch,1) != 1) { cleandied(); return; }
+ if (ch != '+')
+  {
+   log3("warning: qmail-clean unable to clean up ",fn.s,"\n");
+   return;
+  }
+
+ pe.id = id; pe.dt = now();
+ for (c = 0;c < CHANNELS;++c)
+   if (flagchan[c])
+     while (!prioq_insert(&pqchan[c],&pe)) nomem();
+
+ for (c = 0;c < CHANNELS;++c) if (flagchan[c]) break;
+ if (c == CHANNELS)
+   while (!prioq_insert(&pqdone,&pe)) nomem();
+
+ return;
+
+ fail:
+ if (fd != -1) close(fd);
+ if (fdinfo != -1) close(fdinfo);
+ for (c = 0;c < CHANNELS;++c)
+   if (fdchan[c] != -1) close(fdchan[c]);
+}
+
+
+/* this file is too long ---------------------------------------------- MAIN */
+
+int getcontrols() { if (control_init() == -1) return 0;
+ if (control_readint(&lifetime,"control/queuelifetime") == -1) return 0;
+ if (control_readint(&concurrency[0],"control/concurrencylocal") == -1) return 0;
+ if (control_readint(&concurrency[1],"control/concurrencyremote") == -1) return 0;
+ if (control_rldef(&envnoathost,"control/envnoathost",1,"envnoathost") != 1) return 0;
+ if (control_rldef(&bouncefrom,"control/bouncefrom",0,"MAILER-DAEMON") != 1) return 0;
+ if (control_rldef(&bouncehost,"control/bouncehost",1,"bouncehost") != 1) return 0;
+ if (control_rldef(&doublebouncehost,"control/doublebouncehost",1,"doublebouncehost") != 1) return 0;
+ if (control_rldef(&doublebounceto,"control/doublebounceto",0,"postmaster") != 1) return 0;
+ if (!stralloc_cats(&doublebounceto,"@")) return 0;
+ if (!stralloc_cat(&doublebounceto,&doublebouncehost)) return 0;
+ if (!stralloc_0(&doublebounceto)) return 0;
+ if (control_readfile(&locals,"control/locals",1) != 1) return 0;
+ if (!constmap_init(&maplocals,locals.s,locals.len,0)) return 0;
+ switch(control_readfile(&percenthack,"control/percenthack",0))
+  {
+   case -1: return 0;
+   case 0: if (!constmap_init(&mappercenthack,"",0,0)) return 0; break;
+   case 1: if (!constmap_init(&mappercenthack,percenthack.s,percenthack.len,0)) return 0; break;
+  }
+ switch(control_readfile(&redir,"control/recipientmap",0))
+  {
+   case -1: return 0;
+   case 0: if (!constmap_init(&mapredir,"",0,1)) return 0; break;
+   case 1: if (!constmap_init(&mapredir,redir.s,redir.len,1)) return 0; break;
+  }
+ switch(control_readfile(&vdoms,"control/virtualdomains",0))
+  {
+   case -1: return 0;
+   case 0: if (!constmap_init(&mapvdoms,"",0,1)) return 0; break;
+   case 1: if (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) return 0; break;
+  }
+ return 1; }
+
+stralloc newlocals = {0};
+stralloc newvdoms = {0};
+
+void regetcontrols()
+{
+ int r;
+
+ if (control_readfile(&newlocals,"control/locals",1) != 1)
+  { log1("alert: unable to reread control/locals\n"); return; }
+ r = control_readfile(&newvdoms,"control/virtualdomains",0);
+ if (r == -1)
+  { log1("alert: unable to reread control/virtualdomains\n"); return; }
+
+ constmap_free(&maplocals);
+ constmap_free(&mapvdoms);
+
+ while (!stralloc_copy(&locals,&newlocals)) nomem();
+ while (!constmap_init(&maplocals,locals.s,locals.len,0)) nomem();
+
+ if (r)
+  {
+   while (!stralloc_copy(&vdoms,&newvdoms)) nomem();
+   while (!constmap_init(&mapvdoms,vdoms.s,vdoms.len,1)) nomem();
+  }
+ else
+   while (!constmap_init(&mapvdoms,"",0,1)) nomem();
+}
+
+void reread()
+{
+ if (chdir(auto_qmail) == -1)
+  {
+   log1("alert: unable to reread controls: unable to switch to home directory\n");
+   return;
+  }
+ regetcontrols();
+ while (chdir("queue") == -1)
+  {
+   log1("alert: unable to switch back to queue directory; HELP! sleeping...\n");
+   sleep(10);
+  }
+}
+
+void main()
+{
+ int fd;
+ datetime_sec wakeup;
+ fd_set rfds;
+ fd_set wfds;
+ int nfds;
+ struct timeval tv;
+ int c;
+
+ if (chdir(auto_qmail) == -1)
+  { log1("alert: cannot start: unable to switch to home directory\n"); _exit(111); }
+ if (!getcontrols())
+  { log1("alert: cannot start: unable to read controls\n"); _exit(111); }
+ if (chdir("queue") == -1)
+  { log1("alert: cannot start: unable to switch to queue directory\n"); _exit(111); }
+ sig_pipeignore();
+ sig_termcatch(sigterm);
+ sig_alarmcatch(sigalrm);
+ sig_hangupcatch(sighup);
+ sig_childdefault();
+ umask(077);
+
+ fd = open_write("lock/sendmutex");
+ if (fd == -1)
+  { log1("alert: cannot start: unable to open mutex\n"); _exit(111); }
+ if (lock_exnb(fd) == -1)
+  { log1("alert: cannot start: qmail-send is already running\n"); _exit(111); }
+
+ numjobs = 0;
+ for (c = 0;c < CHANNELS;++c)
+  {
+   char ch;
+   int u;
+   int r;
+   do
+     r = read(chanfdin[c],&ch,1);
+   while ((r == -1) && (errno == error_intr));
+   if (r < 1)
+    { log1("alert: cannot start: hath the daemon spawn no fire?\n"); _exit(111); }
+   u = (unsigned int) (unsigned char) ch;
+   if (concurrency[c] > u) concurrency[c] = u;
+   numjobs += concurrency[c];
+  }
+
+ log1("running\n");
+
+ fnmake_init();
+
+ for (c = 0;c < CHANNELS;++c)
+   if (!concurrency[c])
+     log1(channodelmsg[c]);
+
+ comm_init();
+
+ pqstart();
+ job_init();
+ del_init();
+ pass_init();
+ todo_init();
+ cleanup_init();
+
+ while (!flagexitasap || !del_canexit())
+  {
+   if (flagexitasap) del_saywhynoexit();
+
+   recent = now();
+
+   if (flagrunasap) { flagrunasap = 0; pqrun(); }
+   if (flagreadasap) { flagreadasap = 0; reread(); }
+
+   wakeup = recent + SLEEP_FOREVER;
+   FD_ZERO(&rfds);
+   FD_ZERO(&wfds);
+   nfds = 1;
+
+   comm_selprep(&nfds,&wfds);
+   del_selprep(&nfds,&rfds);
+   pass_selprep(&wakeup);
+   todo_selprep(&nfds,&rfds,&wakeup);
+   cleanup_selprep(&wakeup);
+
+   if (wakeup <= recent) tv.tv_sec = 0;
+   else tv.tv_sec = wakeup - recent + SLEEP_FUZZ;
+   tv.tv_usec = 0;
+
+   if (select(nfds,&rfds,&wfds,(fd_set *) 0,&tv) == -1)
+     if (errno == error_intr)
+       ;
+     else
+       log1("warning: trouble in select\n");
+   else
+    {
+     recent = now();
+
+     comm_do(&wfds);
+     del_do(&rfds);
+     todo_do(&rfds);
+     pass_do();
+     cleanup_do();
+    }
+  }
+ pqfinish();
+ log1("exiting\n");
+ _exit(0);
+}
diff --git a/qmail-showctl.8 b/qmail-showctl.8
new file mode 100644 (file)
index 0000000..e6a211d
--- /dev/null
@@ -0,0 +1,12 @@
+.TH qmail-showctl 8
+.SH NAME
+qmail-showctl \- analyze the qmail configuration files
+.SH SYNOPSIS
+.B qmail-showctl
+.SH DESCRIPTION
+.B qmail-showctl
+explains the current
+.B qmail
+configuration.
+.SH "SEE ALSO"
+qmail-control(8)
diff --git a/qmail-showctl.c b/qmail-showctl.c
new file mode 100644 (file)
index 0000000..5134ebc
--- /dev/null
@@ -0,0 +1,233 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "exit.h"
+#include "fmt.h"
+#include "str.h"
+#include "control.h"
+#include "constmap.h"
+#include "stralloc.h"
+#include "direntry.h"
+#include "auto_qmail.h"
+
+stralloc me = {0};
+int meok;
+
+stralloc line = {0};
+char num[FMT_ULONG];
+
+void safeput(buf,len)
+char *buf;
+unsigned int len;
+{
+  char ch;
+
+  while (len > 0) {
+    ch = *buf;
+    if ((ch < 32) || (ch > 126)) ch = '?';
+    substdio_put(subfdout,&ch,1);
+    ++buf;
+    --len;
+  }
+}
+
+void do_int(fn,def,pre,post)
+char *fn;
+char *def;
+char *pre;
+char *post;
+{
+  int i;
+  substdio_puts(subfdout,"\n");
+  substdio_puts(subfdout,fn);
+  substdio_puts(subfdout,": ");
+  switch(control_readint(&i,fn)) {
+    case 0:
+      substdio_puts(subfdout,"(Default.) ");
+      substdio_puts(subfdout,pre);
+      substdio_puts(subfdout,def);
+      substdio_puts(subfdout,post);
+      substdio_puts(subfdout,".\n");
+      break;
+    case 1:
+      if (i < 0) i = 0;
+      substdio_puts(subfdout,pre);
+      substdio_put(subfdout,num,fmt_uint(num,i));
+      substdio_puts(subfdout,post);
+      substdio_puts(subfdout,".\n");
+      break;
+    default:
+      substdio_puts(subfdout,"Oops! Trouble reading this file.\n");
+      break;
+  }
+}
+
+void do_str(fn,flagme,def,pre)
+char *fn;
+int flagme;
+char *def;
+char *pre;
+{
+  substdio_puts(subfdout,"\n");
+  substdio_puts(subfdout,fn);
+  substdio_puts(subfdout,": ");
+  switch(control_readline(&line,fn)) {
+    case 0:
+      substdio_puts(subfdout,"(Default.) ");
+      if (!stralloc_copys(&line,def)) {
+       substdio_puts(subfdout,"Oops! Out of memory.\n");
+       break;
+      }
+      if (flagme && meok)
+       if (!stralloc_copy(&line,&me)) {
+         substdio_puts(subfdout,"Oops! Out of memory.\n");
+         break;
+       }
+    case 1:
+      substdio_puts(subfdout,pre);
+      safeput(line.s,line.len);
+      substdio_puts(subfdout,".\n");
+      break;
+    default:
+      substdio_puts(subfdout,"Oops! Trouble reading this file.\n");
+      break;
+  }
+}
+
+void do_lst(fn,def,pre,post)
+char *fn;
+char *def;
+char *pre;
+char *post;
+{
+  int i;
+  int j;
+
+  substdio_puts(subfdout,"\n");
+  substdio_puts(subfdout,fn);
+  substdio_puts(subfdout,": ");
+  switch(control_readfile(&line,fn)) {
+    case 0:
+      substdio_puts(subfdout,"(Default.) ");
+      substdio_puts(subfdout,def);
+      substdio_puts(subfdout,"\n");
+      break;
+    case 1:
+      substdio_puts(subfdout,"\n");
+      i = 0;
+      for (j = 0;j < line.len;++j)
+       if (!line.s[j]) {
+          substdio_puts(subfdout,pre);
+          safeput(line.s + i,j - i);
+          substdio_puts(subfdout,post);
+          substdio_puts(subfdout,"\n");
+         i = j + 1;
+       }
+      break;
+    default:
+      substdio_puts(subfdout,"Oops! Trouble reading this file.\n");
+      break;
+  }
+}
+
+void main()
+{
+  DIR *dir;
+  direntry *d;
+
+  substdio_puts(subfdout,"The qmail control files are stored in ");
+  substdio_puts(subfdout,auto_qmail);
+  substdio_puts(subfdout,"/control.\n");
+
+  if (chdir(auto_qmail) == -1) {
+    substdio_puts(subfdout,"Oops! Unable to chdir to ");
+    substdio_puts(subfdout,auto_qmail);
+    substdio_puts(subfdout,".\n");
+    substdio_flush(subfdout);
+    _exit(111);
+  }
+  if (chdir("control") == -1) {
+    substdio_puts(subfdout,"Oops! Unable to chdir to control.\n");
+    substdio_flush(subfdout);
+    _exit(111);
+  }
+
+  dir = opendir(".");
+  if (!dir) {
+    substdio_puts(subfdout,"Oops! Unable to open current directory.\n");
+    substdio_flush(subfdout);
+    _exit(111);
+  }
+
+  meok = control_readline(&me,"me");
+  if (meok == -1) {
+    substdio_puts(subfdout,"Oops! Trouble reading control/me.");
+    substdio_flush(subfdout);
+    _exit(111);
+  }
+
+  do_lst("badmailfrom","Any MAIL FROM is allowed.",""," not accepted in MAIL FROM.");
+  do_str("bouncefrom",0,"MAILER-DAEMON","Bounce user name is ");
+  do_str("bouncehost",1,"bouncehost","Bounce host name is ");
+  do_int("concurrencylocal","10","Local concurrency is ","");
+  do_int("concurrencyremote","20","Remote concurrency is ","");
+  do_str("defaultdomain",1,"defaultdomain","Default domain name is ");
+  do_str("defaulthost",1,"defaulthost","Default host name is ");
+  do_str("doublebouncehost",1,"doublebouncehost","2B recipient host: ");
+  do_str("doublebounceto",0,"postmaster","2B recipient user: ");
+  do_str("envnoathost",1,"envnoathost","Presumed domain name is ");
+  do_str("helohost",1,"helohost","SMTP client HELO host name is ");
+  do_str("idhost",1,"idhost","Message-ID host name is ");
+  do_str("localiphost",1,"localiphost","Local IP address becomes ");
+  do_lst("locals","Messages for me are delivered locally.","Messages for "," are delivered locally.");
+  do_str("me",0,"undefined! Uh-oh","My name is ");
+  do_lst("percenthack","The percent hack is not allowed.","The percent hack is allowed for user%host@",".");
+  do_str("plusdomain",1,"plusdomain","Plus domain name is ");
+  do_int("queuelifetime","604800","Message lifetime in the queue is "," seconds");
+  do_lst("rcpthosts","SMTP clients may send messages to any recipient.","SMTP clients may send messages to recipients at ",".");
+  do_lst("recipientmap","No redirections.","Redirection: ","");
+  do_str("smtpgreeting",1,"smtpgreeting","SMTP greeting: 220 ");
+  do_lst("smtproutes","No artificial SMTP routes.","SMTP route: ","");
+  do_int("timeoutconnect","60","SMTP client connection timeout is "," seconds");
+  do_int("timeoutremote","1200","SMTP client data timeout is "," seconds");
+  do_int("timeoutsmtpd","1200","SMTP server data timeout is "," seconds");
+  do_lst("virtualdomains","No virtual domains.","Virtual domain: ","");
+
+  while (d = readdir(dir)) {
+    if (str_equal(d->d_name,".")) continue;
+    if (str_equal(d->d_name,"..")) continue;
+    if (str_equal(d->d_name,"bouncefrom")) continue;
+    if (str_equal(d->d_name,"bouncehost")) continue;
+    if (str_equal(d->d_name,"badmailfrom")) continue;
+    if (str_equal(d->d_name,"bouncefrom")) continue;
+    if (str_equal(d->d_name,"bouncehost")) continue;
+    if (str_equal(d->d_name,"concurrencylocal")) continue;
+    if (str_equal(d->d_name,"concurrencyremote")) continue;
+    if (str_equal(d->d_name,"defaultdomain")) continue;
+    if (str_equal(d->d_name,"defaulthost")) continue;
+    if (str_equal(d->d_name,"doublebouncehost")) continue;
+    if (str_equal(d->d_name,"doublebounceto")) continue;
+    if (str_equal(d->d_name,"envnoathost")) continue;
+    if (str_equal(d->d_name,"helohost")) continue;
+    if (str_equal(d->d_name,"idhost")) continue;
+    if (str_equal(d->d_name,"localiphost")) continue;
+    if (str_equal(d->d_name,"locals")) continue;
+    if (str_equal(d->d_name,"me")) continue;
+    if (str_equal(d->d_name,"percenthack")) continue;
+    if (str_equal(d->d_name,"plusdomain")) continue;
+    if (str_equal(d->d_name,"queuelifetime")) continue;
+    if (str_equal(d->d_name,"rcpthosts")) continue;
+    if (str_equal(d->d_name,"recipientmap")) continue;
+    if (str_equal(d->d_name,"smtpgreeting")) continue;
+    if (str_equal(d->d_name,"smtproutes")) continue;
+    if (str_equal(d->d_name,"timeoutconnect")) continue;
+    if (str_equal(d->d_name,"timeoutremote")) continue;
+    if (str_equal(d->d_name,"timeoutsmtpd")) continue;
+    if (str_equal(d->d_name,"virtualdomains")) continue;
+    substdio_puts(subfdout,"\n");
+    substdio_puts(subfdout,d->d_name);
+    substdio_puts(subfdout,": I have no idea what this file does.\n");
+  }
+
+  substdio_flush(subfdout);
+  _exit(0);
+}
diff --git a/qmail-smtpd.8 b/qmail-smtpd.8
new file mode 100644 (file)
index 0000000..f1cb4a2
--- /dev/null
@@ -0,0 +1,125 @@
+.TH qmail-smtpd 8
+.SH NAME
+qmail-smtpd \- receive mail via SMTP
+.SH SYNOPSIS
+.B qmail-smtpd
+.SH DESCRIPTION
+.B qmail-smtpd
+receives mail messages via the Simple Mail Transfer Protocol (SMTP)
+and invokes
+.B qmail-queue
+to deposit them into the outgoing queue.
+.B qmail-smtpd
+must be supplied several environment variables;
+see
+.BR tcp-environ(5) .
+
+.B qmail-smtpd
+is responsible for counting hops.
+It rejects any message with 100 or more 
+.B Received
+or
+.B Delivered-To
+header fields.
+
+.B qmail-smtpd
+supports ESMTP, including the 8BITMIME and PIPELINING options.
+.SH TRANSPARENCY
+.B qmail-smtpd
+converts the SMTP newline convention into the UNIX newline convention
+by converting CR LF into LF.
+
+.B qmail-smtpd
+accepts messages that contain long lines or non-ASCII characters,
+even though such messages violate the SMTP protocol.
+.SH "CONTROL FILES"
+.TP 5
+.I badmailfrom
+Unacceptable envelope sender addresses.
+.B qmail-smtpd
+will reject every recipient address for a message
+if the envelope sender address is listed in
+.IR badmailfrom .
+A line in
+.I badmailfrom
+may be of the form
+.BR @\fIhost ,
+meaning every address at
+.IR host .
+.TP 5
+.I localiphost
+Replacement host name for local IP addresses.
+Default:
+.IR me ,
+if that is supplied.
+.B qmail-smtpd
+is responsible for recognizing dotted-decimal addresses for the
+current host.
+When it sees a recipient address of the form
+.IR box@[d.d.d.d] ,
+where
+.I d.d.d.d
+is a local IP address,
+it replaces
+.IR [d.d.d.d]
+with
+.IR localiphost .
+This is done before
+.IR rcpthosts .
+.TP 5
+.I rcpthosts
+Allowed RCPT domains.
+If
+.I rcpthosts
+is supplied,
+.B qmail-smtpd
+will reject
+any envelope recipient address with a domain not listed in
+.IR rcpthosts .
+
+Exception:
+If the environment variable
+.B RELAYCLIENT
+is set,
+.B qmail-smtpd
+will ignore
+.IR rcpthosts ,
+and will append the value of
+.B RELAYCLIENT
+to each incoming recipient address.
+
+.I rcpthosts
+may include wildcards:
+
+.EX
+   heaven.af.mil
+   .heaven.af.mil
+.EE
+
+Envelope recipient addresses without @ signs are
+always allowed through.
+.TP 5
+.I smtpgreeting
+SMTP greeting message.
+Default:
+.IR me ,
+if that is supplied;
+otherwise
+.B qmail-smtpd
+will refuse to run.
+The first word of
+.I smtpgreeting
+should be the current host's name.
+.TP 5
+.I timeoutsmtpd
+Number of seconds
+.B qmail-smtpd
+will wait for each new buffer of data from the remote SMTP client.
+Default: 1200.
+.SH "SEE ALSO"
+tcp-env(1),
+tcp-environ(5),
+qmail-control(5),
+qmail-inject(8),
+qmail-queue(8),
+qmail-remote(8)
diff --git a/qmail-smtpd.c b/qmail-smtpd.c
new file mode 100644 (file)
index 0000000..50cfc0a
--- /dev/null
@@ -0,0 +1,449 @@
+#include "sig.h"
+#include "readwrite.h"
+#include "getln.h"
+#include "stralloc.h"
+#include "substdio.h"
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "control.h"
+#include "received.h"
+#include "constmap.h"
+#include "error.h"
+#include "ipme.h"
+#include "ip.h"
+#include "qmail.h"
+#include "str.h"
+#include "fmt.h"
+#include "byte.h"
+#include "case.h"
+#include "env.h"
+#include "now.h"
+#include "exit.h"
+
+#define MAXHOPS 100
+int timeout = 1200;
+
+char ssoutbuf[512];
+substdio ssout = SUBSTDIO_FDBUF(write,1,ssoutbuf,sizeof(ssoutbuf));
+
+void die() { substdio_flush(&ssout); _exit(1); }
+void flush() { if (substdio_flush(&ssout) == -1) _exit(1); }
+void out(s) char *s; { if (substdio_puts(&ssout,s) == -1) die(); }
+
+int timeoutread(fd,buf,n) int fd; char *buf; int n;
+{
+ int r; int saveerrno;
+ flush();
+ alarm(timeout);
+ r = read(fd,buf,n); saveerrno = errno;
+ alarm(0);
+ errno = saveerrno; return r;
+}
+
+char ssinbuf[1024];
+substdio ssin = SUBSTDIO_FDBUF(timeoutread,0,ssinbuf,sizeof(ssinbuf));
+
+
+void outofmem() { out("421 out of memory (#4.3.0)\r\n"); die(); }
+void sigalrm() { out("451 timeout (#4.4.2)\r\n"); die(); }
+
+struct qmail qqt;
+stralloc greeting = {0};
+int liphostok = 0;
+stralloc liphost = {0};
+int rhok = 0;
+stralloc rcpthosts = {0};
+struct constmap maprcpthosts;
+int bmfok = 0;
+stralloc bmf = {0};
+struct constmap mapbmf;
+int flagbarf; /* defined if seenmail */
+
+stralloc helohost = {0};
+stralloc mailfrom = {0};
+stralloc rcptto = {0};
+int seenmail = 0;
+
+stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
+
+char *remoteip;
+char *remotehost;
+char *remoteinfo;
+char *local;
+char *relayclient;
+
+void dohelo(arg) char *arg;
+{
+ if (!stralloc_copys(&helohost,arg)) outofmem(); 
+ if (!stralloc_0(&helohost)) outofmem(); 
+}
+
+void getenvs()
+{
+ remoteip = env_get("TCPREMOTEIP");
+ if (!remoteip) remoteip = "unknown";
+ local = env_get("TCPLOCALHOST");
+ if (!local) local = env_get("TCPLOCALIP");
+ if (!local) local = "unknown";
+ remotehost = env_get("TCPREMOTEHOST");
+ if (!remotehost) remotehost = "unknown";
+ remoteinfo = env_get("TCPREMOTEINFO");
+ relayclient = env_get("RELAYCLIENT");
+ dohelo(remotehost);
+}
+
+void straynewline()
+{
+ out("451 \
+Put ,E=\\r\\n at the end of Mether, Mtcp, or Msmtp in sendmail.cf \
+if you are using Solaris 2.5 (fixed in 2.5.1). \
+I cannot accept messages with stray newlines. \
+Many SMTP servers will time out waiting for \\r\\n.\\r\\n.\
+\r\n");
+ die();
+}
+
+void blast(ssfrom,hops)
+substdio *ssfrom;
+int *hops;
+{
+ char ch;
+ int state;
+ int flaginheader;
+ int pos; /* number of bytes since most recent \n, if fih */
+ int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
+ int flagmaybey; /* 1 if this line might match \r\n, if fih */
+ int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
+
+ state = 1;
+ *hops = 0;
+ flaginheader = 1;
+ pos = 0; flagmaybex = flagmaybey = flagmaybez = 1;
+ for (;;)
+  {
+   if (substdio_get(ssfrom,&ch,1) <= 0) die();
+   if (flaginheader)
+    {
+     if (pos < 9)
+      {
+       if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;
+       if (flagmaybez) if (pos == 8) ++*hops;
+       if (pos < 8)
+         if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;
+       if (flagmaybex) if (pos == 7) ++*hops;
+       if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0;
+       if (flagmaybey) if (pos == 1) flaginheader = 0;
+      }
+     ++pos;
+     if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }
+    }
+   switch(state)
+    {
+     case 0:
+       if (ch == '\n') straynewline();
+       if (ch == '\r') { state = 4; continue; }
+       break;
+     case 1: /* \r\n */
+       if (ch == '\n') straynewline();
+       if (ch == '.') { state = 2; continue; }
+       if (ch == '\r') { state = 4; continue; }
+       state = 0;
+       break;
+     case 2: /* \r\n + . */
+       if (ch == '\n') straynewline();
+       if (ch == '\r') { state = 3; continue; }
+       state = 0;
+       break;
+     case 3: /* \r\n + .\r */
+       if (ch == '\n') return;
+       qmail_put(&qqt,".\r",2);
+       if (ch == '\r') { state = 4; continue; }
+       state = 0;
+       break;
+     case 4: /* + \r */
+       if (ch == '\n') { state = 1; break; }
+       if (ch != '\r') { qmail_put(&qqt,"\r",1); state = 0; }
+    }
+   qmail_put(&qqt,&ch,1);
+  }
+}
+
+int addrparse(arg)
+char *arg;
+{
+ int i;
+ char ch;
+ struct ip_address ip;
+ int flagesc;
+ int flagquoted;
+
+ arg += str_chr(arg,'<');
+ if (*arg != '<') return 0;
+ ++arg;
+
+ /* strip source route */
+ if (*arg == '@') while (*arg) if (*arg++ == ':') break;
+
+ if (!*arg) return 0;
+ if (!stralloc_copys(&addr,"")) outofmem();
+ flagesc = 0;
+ flagquoted = 0;
+ for (i = 0;ch = arg[i];++i) /* copy arg to addr, stripping quotes */
+  {
+   if (flagesc)
+    { if (!stralloc_append(&addr,&ch)) outofmem(); flagesc = 0; }
+   else
+    {
+     if (!flagquoted && (ch == '>')) break;
+     switch(ch)
+      {
+       case '\\': flagesc = 1; break;
+       case '"': flagquoted = !flagquoted; break;
+       default: if (!stralloc_append(&addr,&ch)) outofmem();
+      }
+    }
+  }
+ if (!ch) return 0;
+ if (!stralloc_append(&addr,"")) outofmem();
+ ++i;
+ while (arg[i])
+  {
+   if (!case_diffs(arg + i," BODY=8BITMIME")) i += 14;
+   else if (!case_diffs(arg + i," BODY=7BIT")) i += 10;
+   else return 0;
+  }
+
+ if (liphostok)
+  {
+   i = byte_rchr(addr.s,addr.len,'@');
+   if (i < addr.len) /* if not, partner should go read rfc 821 */
+     if (addr.s[i + 1] == '[')
+       if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)])
+         if (ipme_is(&ip))
+          {
+           addr.len = i + 1;
+           if (!stralloc_cat(&addr,&liphost)) outofmem();
+           if (!stralloc_0(&addr)) outofmem();
+          }
+  }
+
+ return 1;
+}
+
+int addrallowed()
+{
+ int j;
+ if (!rhok) return 1;
+ j = byte_rchr(addr.s,addr.len,'@');
+ if (j >= addr.len) return 1; /* can be taken care of by envnoathost */
+ if (constmap(&maprcpthosts,addr.s + j + 1,addr.len - j - 2)) return 1;
+ for (;j < addr.len;++j)
+   if (addr.s[j] == '.')
+     if (constmap(&maprcpthosts,addr.s + j,addr.len - j - 1)) return 1;
+ return 0;
+}
+
+void bmfcheck()
+{
+ int j;
+ flagbarf = 0;
+ if (!bmfok) return;
+ if (constmap(&mapbmf,addr.s,addr.len - 1)) { flagbarf = 1; return; }
+ j = byte_rchr(addr.s,addr.len,'@');
+ if (j < addr.len)
+   if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) flagbarf = 1;
+}
+
+void smtp_greet(code) char *code; {
+ if (substdio_puts(&ssout,code) == -1) die();
+ if (substdio_put(&ssout,greeting.s,greeting.len) == -1) die(); }
+void smtp_quit() { smtp_greet("221 "); out("\r\n"); die(); }
+void smtp_help() { out("214-qmail home page: http://pobox.com/~djb/qmail.html\r\n214 send comments to qmail@pobox.com\r\n"); }
+void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
+void err_bmf() { out("553 sorry, your envelope sender is in my badmailfrom list (#5.7.1)\r\n"); }
+void err_nogateway() { out("553 sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1)\r\n"); }
+void err_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
+void err_seenmail() { out("503 one MAIL per message (#5.5.1)\r\n"); }
+void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
+void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }
+void err_noop() { out("250 ok\r\n"); }
+void err_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
+void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
+void smtp_helo(arg) char *arg; {
+ smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
+ seenmail = 0;
+ dohelo(arg ? arg : ""); }
+void smtp_rset() {
+ seenmail = 0;
+ out("250 flushed\r\n"); }
+void smtp_mail(arg) char *arg; {
+ if (seenmail) { err_seenmail(); return; }
+ if (!arg) { err_syntax(); return; }
+ if (!addrparse(arg)) { err_syntax(); return; }
+ bmfcheck();
+ seenmail = 1; out("250 ok\r\n");
+ if (!stralloc_copys(&rcptto,"")) outofmem();
+ if (!stralloc_copys(&mailfrom,addr.s)) outofmem();
+ if (!stralloc_0(&mailfrom)) outofmem(); }
+void smtp_rcpt(arg) char *arg; {
+ if (!seenmail) { err_wantmail(); return; }
+ if (!arg) { err_syntax(); return; }
+ if (!addrparse(arg)) { err_syntax(); return; }
+ if (flagbarf) { err_bmf(); return; }
+ if (relayclient)
+  {
+   --addr.len;
+   if (!stralloc_cats(&addr,relayclient)) outofmem();
+   if (!stralloc_0(&addr)) outofmem();
+  }
+ else
+   if (!addrallowed()) { err_nogateway(); return; }
+ out("250 ok\r\n");
+ if (!stralloc_cats(&rcptto,"T")) outofmem();
+ if (!stralloc_cats(&rcptto,addr.s)) outofmem();
+ if (!stralloc_0(&rcptto)) outofmem(); }
+
+char accept_buf[FMT_ULONG];
+void acceptmessage(qp) unsigned long qp;
+{
+ datetime_sec when;
+ when = now();
+ out("250 ok ");
+ accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;
+ out(accept_buf);
+ out(" qp ");
+ accept_buf[fmt_ulong(accept_buf,qp)] = 0;
+ out(accept_buf);
+ out("\r\n");
+}
+
+void smtp_data() {
+ int hops; int r; unsigned long qp;
+ if (!seenmail) { err_wantmail(); return; }
+ if (!rcptto.len) { err_wantrcpt(); return; }
+ seenmail = 0;
+ if (qmail_open(&qqt) == -1) { err_qqt(); return; }
+ qp = qmail_qp(&qqt);
+ out("354 go ahead\r\n");
+
+ received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,case_diffs(remotehost,helohost.s) ? helohost.s : 0);
+ blast(&ssin,&hops);
+ hops = (hops >= MAXHOPS);
+ if (hops) qmail_fail(&qqt);
+ qmail_from(&qqt,mailfrom.s);
+ qmail_put(&qqt,rcptto.s,rcptto.len);
+
+ r = qmail_close(&qqt);
+ if (!r) { acceptmessage(qp); return; }
+ if (hops) { out("554 too many hops, this message is looping (#5.4.6)\r\n"); return; }
+ switch(r)
+  {
+   case QMAIL_TOOLONG: out("554 address too long (#5.1.3)\r\n"); return;
+   case QMAIL_SYS: out("451 qq system error (#4.3.0)\r\n"); return;
+   case QMAIL_READ: out("451 qq read error (#4.3.0)\r\n"); return;
+   case QMAIL_WRITE: out("451 qq write error or disk full (#4.3.0)\r\n"); return;
+   case QMAIL_NOMEM: out("451 qq out of memory (#4.3.0)\r\n"); return;
+   case QMAIL_EXECSOFT: out("451 could not exec qq (#4.3.0)\r\n"); return;
+   case QMAIL_TIMEOUT: out("451 qq timeout (#4.3.0)\r\n"); return;
+   case QMAIL_WAITPID: out("451 qq waitpid surprise (#4.3.0)\r\n"); return;
+   case QMAIL_CRASHED: out("451 qq crashed (#4.3.0)\r\n"); return;
+   case QMAIL_USAGE: out("451 qq usage surprise (#4.3.0)\r\n"); return;
+   default: out("451 qq internal bug (#4.3.0)\r\n"); return;
+  }
+}
+
+static struct { void (*fun)(); char *text; int flagflush; } smtpcmd[] = {
+  { smtp_rcpt, "rcpt", 0 }
+, { smtp_mail, "mail", 0 }
+, { smtp_data, "data", 1 }
+, { smtp_quit, "quit", 1 }
+, { smtp_helo, "helo", 1 }
+, { smtp_helo, "ehlo", 1 }
+, { smtp_rset, "rset", 0 }
+, { smtp_help, "help", 1 }
+, { err_noop, "noop", 1 }
+, { err_vrfy, "vrfy", 1 }
+, { 0, 0, 0 }
+};
+
+void doit(cmd)
+char *cmd;
+{
+ int i;
+ int j;
+ char ch;
+
+ for (i = 0;smtpcmd[i].fun;++i)
+  {
+   for (j = 0;ch = smtpcmd[i].text[j];++j)
+     if ((cmd[j] != ch) && (cmd[j] != ch - 32))
+       break;
+   if (!ch)
+     if (!cmd[j] || (cmd[j] == ' '))
+      {
+       while (cmd[j] == ' ') ++j;
+       if (!cmd[j])
+         smtpcmd[i].fun((char *) 0);
+       else
+         smtpcmd[i].fun(cmd + j);
+       if (smtpcmd[i].flagflush) flush();
+       return;
+      }
+  }
+ err_unimpl();
+ flush();
+}
+
+void getcontrols()
+{
+ if (control_init() == -1) die();
+ if (control_rldef(&greeting,"control/smtpgreeting",1,(char *) 0) != 1) die();
+ switch(control_rldef(&liphost,"control/localiphost",1,(char *) 0))
+  { case -1: die(); case 1: liphostok = 1; }
+ if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die();
+ if (timeout <= 0) timeout = 1;
+ switch(control_readfile(&rcpthosts,"control/rcpthosts",0))
+  {
+   case -1: die();
+   case 1:
+     rhok = 1;
+     if (!constmap_init(&maprcpthosts,rcpthosts.s,rcpthosts.len,0)) die();
+  }
+ switch(control_readfile(&bmf,"control/badmailfrom",0))
+  {
+   case -1: die();
+   case 1:
+     bmfok = 1;
+     if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die();
+  }
+}
+
+void main()
+{
+ static stralloc cmd = {0};
+ int match;
+
+ sig_alarmcatch(sigalrm);
+ sig_pipeignore();
+
+ if (chdir(auto_qmail) == -1) die();
+ getcontrols();
+ getenvs();
+
+ if (ipme_init() != 1) die();
+
+ smtp_greet("220 ");
+ out(" ESMTP\r\n");
+
+ for (;;)
+  {
+   /* XXX: recipient can contain quoted lf. aargh. */
+   if (getln(&ssin,&cmd,&match,'\n') == -1) die();
+   if (!match) die();
+   if (cmd.len == 0) die();
+   if (cmd.s[--cmd.len] != '\n') die();
+   if ((cmd.len > 0) && (cmd.s[cmd.len - 1] == '\r')) --cmd.len;
+   cmd.s[cmd.len++] = 0;
+   doit(cmd.s);
+  }
+}
diff --git a/qmail-start.9 b/qmail-start.9
new file mode 100644 (file)
index 0000000..95db54f
--- /dev/null
@@ -0,0 +1,91 @@
+.TH qmail-start 8
+.SH NAME
+qmail-start \- turn on mail delivery
+.SH SYNOPSIS
+.B qmail-start
+[
+.I aliasempty
+[
+.I logger arg ...
+]
+]
+.SH DESCRIPTION
+.B qmail-start
+invokes
+.BR qmail-send ,
+.BR qmail-lspawn ,
+.BR qmail-rspawn ,
+and
+.BR qmail-clean .
+These four daemons cooperate to deliver messages from the queue.
+
+.B qmail-start
+arranges for
+.BR qmail-send 's
+activity record to be sent to
+.BR qmail-start 's
+output.
+See
+.B qmail-log(5)
+for the format of the activity record.
+Other than this,
+.B qmail-start
+does not print anything, even on failure.
+
+If
+.I aliasempty
+is supplied,
+.B qmail-start
+passes it to
+.BR qmail-lspawn .
+
+If
+.I logger
+is supplied,
+.B qmail-start
+invokes
+.I logger
+with the given arguments,
+and feeds
+.BR qmail-send 's
+activity record through
+.IR logger .
+
+Environment variables given to
+.B qmail-start
+will eventually be passed on to
+.BR qmail-local ,
+so make sure to clean up the environment if you run
+.B qmail-start
+manually:
+
+.EX
+   # env - PATH="QMAILHOME/bin:$PATH"
+.br
+   qmail-start ./Mailbox splogger qmail &
+.br
+   (all on one line)
+.EE
+
+.B qmail-start
+sets the uid and gid of each daemon properly.
+
+Note that
+.B qmail-send
+normally juggles several simultaneous deliveries.
+To reduce
+.BR qmail-send 's
+impact on other programs,
+you can run
+.B qmail-start
+with a low priority.
+.SH "SEE ALSO"
+logger(1),
+splogger(1),
+nice(1),
+qmail-log(5),
+qmail-local(8),
+qmail-clean(8),
+qmail-lspawn(8),
+qmail-rspawn(8),
+qmail-send(8)
diff --git a/qmail-start.c b/qmail-start.c
new file mode 100644 (file)
index 0000000..37423b0
--- /dev/null
@@ -0,0 +1,120 @@
+#include "fd.h"
+#include "prot.h"
+#include "exit.h"
+#include "fork.h"
+#include "auto_uids.h"
+
+char *(qsargs[]) = { "qmail-send", 0 };
+char *(qcargs[]) = { "qmail-clean", 0 };
+char *(qlargs[]) = { "qmail-lspawn", "./Mailbox", 0 };
+char *(qrargs[]) = { "qmail-rspawn", 0 };
+
+void die() { _exit(111); }
+
+int pi0[2];
+int pi1[2];
+int pi2[2];
+int pi3[2];
+int pi4[2];
+int pi5[2];
+int pi6[2];
+
+void close23456() { close(2); close(3); close(4); close(5); close(6); }
+
+void closepipes() {
+  close(pi1[0]); close(pi1[1]); close(pi2[0]); close(pi2[1]);
+  close(pi3[0]); close(pi3[1]); close(pi4[0]); close(pi4[1]);
+  close(pi5[0]); close(pi5[1]); close(pi6[0]); close(pi6[1]);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  if (chdir("/") == -1) die();
+  umask(077);
+  if (prot_gid(auto_gidq) == -1) die();
+
+  if (fd_copy(2,0) == -1) die();
+  if (fd_copy(3,0) == -1) die();
+  if (fd_copy(4,0) == -1) die();
+  if (fd_copy(5,0) == -1) die();
+  if (fd_copy(6,0) == -1) die();
+
+  if (argv[1]) {
+    qlargs[1] = argv[1];
+    ++argv;
+  }
+
+  if (argv[1]) {
+    if (pipe(pi0) == -1) die();
+    switch(fork()) {
+      case -1:
+       die();
+      case 0:
+        if (prot_gid(auto_gidn) == -1) die();
+        if (prot_uid(auto_uidl) == -1) die();
+        close(pi0[1]);
+        if (fd_move(0,pi0[0]) == -1) die();
+        close23456();
+        execvp(argv[1],argv + 1);
+       die();
+    }
+    close(pi0[0]);
+    if (fd_move(1,pi0[1]) == -1) die();
+  }
+  if (pipe(pi1) == -1) die();
+  if (pipe(pi2) == -1) die();
+  if (pipe(pi3) == -1) die();
+  if (pipe(pi4) == -1) die();
+  if (pipe(pi5) == -1) die();
+  if (pipe(pi6) == -1) die();
+  switch(fork()) {
+    case -1: die();
+    case 0:
+      if (fd_copy(0,pi1[0]) == -1) die();
+      if (fd_copy(1,pi2[1]) == -1) die();
+      close23456();
+      closepipes();
+      execvp(*qlargs,qlargs);
+      die();
+  }
+  switch(fork()) {
+    case -1: die();
+    case 0:
+      if (prot_uid(auto_uidr) == -1) die();
+      if (fd_copy(0,pi3[0]) == -1) die();
+      if (fd_copy(1,pi4[1]) == -1) die();
+      close23456();
+      closepipes();
+      execvp(*qrargs,qrargs);
+      die();
+  }
+  switch(fork()) {
+    case -1: die();
+    case 0:
+      if (prot_uid(auto_uidq) == -1) die();
+      if (fd_copy(0,pi5[0]) == -1) die();
+      if (fd_copy(1,pi6[1]) == -1) die();
+      close23456();
+      closepipes();
+      execvp(*qcargs,qcargs);
+      die();
+  }
+  if (prot_uid(auto_uids) == -1) die();
+  if (fd_copy(0,1) == -1) die();
+  if (fd_copy(1,pi1[1]) == -1) die();
+  if (fd_copy(2,pi2[0]) == -1) die();
+  if (fd_copy(3,pi3[1]) == -1) die();
+  if (fd_copy(4,pi4[0]) == -1) die();
+  if (fd_copy(5,pi5[1]) == -1) die();
+  if (fd_copy(6,pi6[0]) == -1) die();
+  closepipes();
+  execvp(*qsargs,qsargs);
+  die();
+}
diff --git a/qmail-tcpto.8 b/qmail-tcpto.8
new file mode 100644 (file)
index 0000000..f305181
--- /dev/null
@@ -0,0 +1,29 @@
+.TH qmail-tcpto 8
+.SH NAME
+qmail-tcpto \- print TCP timeout table
+.SH SYNOPSIS
+.B qmail-tcpto
+.SH DESCRIPTION
+After an SMTP connection attempt times out,
+.B qmail-remote
+records the relevant IP address.
+If the same address fails again (after at least two minutes with
+no intervening successful connections),
+.B qmail-remote
+assumes that further attempts will fail for at least another hour.
+
+.B qmail-tcpto
+prints
+.BR qmail-remote 's
+current list of timeouts.
+
+.B qmail-tcpto
+must be run either as 
+.B root
+or with user id
+.B qmailr
+and group id
+.BR qmail .
+.SH "SEE ALSO"
+qmail-qread(8),
+qmail-remote(8)
diff --git a/qmail-tcpto.c b/qmail-tcpto.c
new file mode 100644 (file)
index 0000000..d181ecf
--- /dev/null
@@ -0,0 +1,85 @@
+/* XXX: this program knows quite a bit about tcpto's internals */
+
+#include "substdio.h"
+#include "subfd.h"
+#include "auto_qmail.h"
+#include "fmt.h"
+#include "ip.h"
+#include "lock.h"
+#include "error.h"
+#include "exit.h"
+#include "datetime.h"
+#include "now.h"
+
+void die(n) int n; { substdio_flush(subfdout); _exit(n); }
+
+void warn(s) char *s;
+{
+ char *x;
+ x = error_str(errno);
+ substdio_puts(subfdout,s);
+ substdio_puts(subfdout,": ");
+ substdio_puts(subfdout,x);
+ substdio_puts(subfdout,"\n");
+}
+
+void die_chdir() { warn("fatal: unable to chdir"); die(111); }
+void die_open() { warn("fatal: unable to open tcpto"); die(111); }
+void die_lock() { warn("fatal: unable to lock tcpto"); die(111); }
+void die_read() { warn("fatal: unable to read tcpto"); die(111); }
+
+char tcpto_buf[1024];
+
+char tmp[FMT_ULONG + IPFMT];
+
+void main()
+{
+ int fdlock;
+ int fd;
+ int r;
+ int i;
+ char *record;
+ struct ip_address ip;
+ datetime_sec when;
+ datetime_sec start;
+
+ if (chdir(auto_qmail) == -1) die_chdir();
+ if (chdir("queue/lock") == -1) die_chdir();
+
+ fdlock = open_write("tcpto");
+ if (fdlock == -1) die_open();
+ fd = open_read("tcpto");
+ if (fd == -1) die_open();
+ if (lock_ex(fdlock) == -1) die_lock();
+ r = read(fd,tcpto_buf,sizeof(tcpto_buf));
+ close(fd);
+ close(fdlock);
+
+ if (r == -1) die_read();
+ r >>= 4;
+
+ start = now();
+
+ record = tcpto_buf;
+ for (i = 0;i < r;++i)
+  {
+   if (record[4] >= 1)
+    {
+     byte_copy(&ip,4,record);
+     when = (unsigned long) (unsigned char) record[11];
+     when = (when << 8) + (unsigned long) (unsigned char) record[10];
+     when = (when << 8) + (unsigned long) (unsigned char) record[9];
+     when = (when << 8) + (unsigned long) (unsigned char) record[8];
+
+     substdio_put(subfdout,tmp,ip_fmt(tmp,&ip));
+     substdio_puts(subfdout," timed out ");
+     substdio_put(subfdout,tmp,fmt_ulong(tmp,(unsigned long) (start - when)));
+     substdio_puts(subfdout," seconds ago; # recent timeouts: ");
+     substdio_put(subfdout,tmp,fmt_ulong(tmp,(unsigned long) (unsigned char) record[4]));
+     substdio_puts(subfdout,"\n");
+    }
+   record += 16;
+  }
+
+ die(0);
+}
diff --git a/qmail-upgrade.9 b/qmail-upgrade.9
new file mode 100644 (file)
index 0000000..82ae6de
--- /dev/null
@@ -0,0 +1,201 @@
+.TH qmail-upgrade 7
+.SH "NAME"
+qmail-upgrade \- user-visible differences between qmail and sendmail
+.SH "INTRODUCTION"
+You will notice some differences
+when the system switches from
+.B sendmail
+to
+.BR qmail .
+.TP 5
+1.
+.B qmail-local
+sends incoming mail to
+.B ~\fIyou\fB/Mailbox
+by default,
+not
+.BR /usr/spool/mail/\fIyou\fB .
+Your system administrator has changed your
+.B MAIL
+environment variable so that your mail reader looks for
+.BR ~\fIyou\fB/Mailbox .
+.B \fR(\fB/usr/spool/mail
+is a massive security problem.)
+.TP 5
+2.
+.B qmail-local
+pays no attention to
+.BR .forward .
+It has a much better mechanism,
+.BR .qmail ,
+so that you can handle not only forwarding
+but even your own mailing lists.
+See below for more details.
+.TP 5
+3.
+.B qmail-local
+pays no attention to
+.BR /etc/aliases .
+Your system administrator
+can use the
+.B .qmail
+mechanism instead.
+See below.
+.TP 5
+4.
+.B qmail
+does not support the
+.B \e\fIyou\fB
+mechanism
+for ignoring aliases.
+The
+.B .qmail
+mechanism is much more flexible;
+see below.
+.TP 5
+5.
+.B qmail-inject
+has a completely different philosophy from
+.B sendmail
+on interpreting non-fully-qualified host names.
+It uses fixed rules, not DNS.
+Some examples at UIC:
+
+.EX
+     russet -> russet.math.uic.edu
+.br
+     newton -> newton.math.uic.edu
+.br
+     ut.ee  -> ut.ee  (a host in Estonia)
+.br
+     ut.ee+ -> ut.ee.uic.edu
+.br
+     uicvm+ -> uicvm.uic.edu
+.EE
+
+Here the
+.I default domain name
+(for hosts without dots)
+is
+.B math.uic.edu\fP,
+and the
+.I plus domain name
+is
+.B uic.edu\fP.
+.TP 5
+6.
+Unlike
+.BR sendmail ,
+.B qmail-inject
+doesn't replace host names with canonical names.
+Example:
+.B qmail-inject
+won't change
+.B postmaster@ftp.cs.berkeley.edu
+in your header to
+.BR postmaster@kohler.cs.berkeley.edu .
+.TP 5
+7.
+.B qmail-local
+adds a new field,
+.BR Delivered-To ,
+before every delivery.
+It uses the contents of
+.B Delivered-To
+to prevent mail forwarding loops.
+.TP 5
+8.
+If you send a message with only
+.B Bcc
+recipients,
+.B qmail-inject
+will add
+.B Cc: recipient list not shown:;\fR,
+rather than
+.BR sendmail 's
+privacy-invading
+.B Apparently-To
+header field.
+.SH "QMAIL MAILING LISTS"
+.B sendmail
+deals with aliases, forwarding, and mailing lists
+at the very heart of the mail system.
+
+.B qmail
+takes a radically different approach.
+It gives you the power to set up your own mailing lists without
+pestering your system administrator.
+
+Under
+.BR qmail ,
+you are in charge of all addresses of the form
+.B \fIyou\fBBREAK\fIanything\fR.
+The delivery of
+.B \fIyou\fBBREAK\fIanything
+is controlled by
+.B ~\fIyou\fB/.qmail-\fIanything\fR,
+a file in your home directory.
+
+For example, if you want to set up a
+bug-of-the-month-club mailing list,
+you can put a list of addresses into
+.BR ~\fIyou\fB/.qmail-botmc .
+Any mail to
+.B \fIyou\fBBREAKbotmc
+will be forwarded to all of those addresses.
+Mail directly to
+.B \fIyou\fB
+is controlled by
+.BR ~\fIyou\fB/.qmail .
+You can even set up a catch-all,
+.BR ~\fIyou\fB/.qmail-default ,
+to handle unknown
+.B \fIyou\fBBREAK
+addresses.
+
+Your
+.B .qmail
+files, like your old
+.BR .forward ,
+may list files,
+forwarding addresses,
+or other programs to run.
+(But beware that the syntax is a bit different;
+see
+.B dot-qmail(5)
+for more details.)
+.B qmail-local
+automatically
+detects forwarding loops the instant they occur,
+even if they happen indirectly through other hosts.
+
+As a helpful special case, if a
+.B .qmail
+file is empty, it refers to
+.BR ~\fIyou\fB/Mailbox .
+For example, if you touch
+.BR ~\fIyou\fB/.qmail-direct ,
+mail for
+.B \fIyou\fBBREAKdirect
+will act like
+.B \e\fIyou\fB
+did under
+.BR sendmail .
+
+Addresses that don't contain a username are handled by the
+.B alias
+user.
+For example, your system administrator has set up
+.B ~alias/.qmail-postmaster
+to handle mail for
+.BR Postmaster .
+(Note to administrators:
+.B ~alias
+doesn't apply to addresses that start with a user name,
+with certain exceptions.)
+.SH "SEE ALSO"
+addresses(5),
+dot-qmail(5),
+envelopes(5),
+qmail-header(8),
+qmail-inject(8)
diff --git a/qmail-upq.sh b/qmail-upq.sh
new file mode 100644 (file)
index 0000000..a068196
--- /dev/null
@@ -0,0 +1,14 @@
+cd QMAIL
+cd queue
+for dir in mess info local remote
+do
+  ( cd $dir; find . -type f -print ) | (
+    cd $dir
+    while read path
+    do
+      id=`basename "$path"`
+      sub=`expr "$id" % SPLIT`
+      mv "$path" "$sub"/"$id"
+    done
+  )
+done
diff --git a/qmail-users.9 b/qmail-users.9
new file mode 100644 (file)
index 0000000..c44a452
--- /dev/null
@@ -0,0 +1,113 @@
+.TH qmail-users 5
+.SH NAME
+qmail-users \- assign mail addresses to users
+.SH OVERVIEW
+The file
+.B QMAILHOME/users/assign
+assigns addresses to users. For example,
+
+.EX
+   =joe.shmoe:joe:503:78:/home/joe:::
+.EE
+
+says that mail for
+.B joe.shmoe
+should be delivered to user
+.BR joe ,
+with uid 503 and gid 78,
+as specified by
+.BR /home/joe/.qmail .
+
+Assignments fed to
+.B qmail-newu
+will be used by
+.B qmail-lspawn
+to control
+.BR qmail-local 's
+deliveries.
+See
+.BR qmail-newu (8).
+A change to
+.B QMAILHOME/users/assign
+will have no effect until
+.B qmail-newu
+is run.
+.SH STRUCTURE
+.B QMAILHOME/users/assign
+is a series of assignments, one per line.
+It ends with a line containing a single dot.
+Lines must not contain NUL.
+.SH "SIMPLE ASSIGNMENTS"
+A simple assignment is a line of the form
+
+.EX
+   =local:user:uid:gid:homedir:dash:ext:
+.EE
+
+Here
+.I local
+is an address;
+.IR user ,
+.IR uid ,
+and
+.I gid
+are the account name, uid, and gid
+of the user in charge of
+.IR local ;
+and messages to
+.I local
+will be controlled by
+.IR homedir\fB/.qmail\fIdashext .
+
+If there are several assignments for the same
+.I local
+address,
+.B qmail-lspawn
+will use the first one.
+
+.I local
+is interpreted without regard to case.
+.SH "WILDCARD ASSIGNMENTS"
+A wildcard assignment is a line of the form
+
+.EX
+   +loc:user:uid:gid:homedir:dash:pre:
+.EE
+
+This assignment applies to any address beginning with
+.IR loc ,
+including
+.I loc
+itself.
+It means the same as
+
+.EX
+   =locext:user:uid:gid:homedir:dash:preext:
+.EE
+
+for every string
+.IR ext .
+
+A more specific wildcard assignment overrides a less specific
+assignment, and a simple assignment overrides any wildcard assignment.
+For example:
+
+.EX
+   +:alias:7790:2108:QMAILHOME/alias:-::
+   +joe-:joe:507:100:/home/joe:-::
+   =joe:joe:507:100:/home/joe:::
+.EE
+
+The address
+.B joe
+is handled by the third line;
+the address
+.B joe-direct
+is handled by the second line;
+the address
+.B bill
+is handled by the first line.
+.SH "SEE ALSO"
+qmail-pw2u(8),
+qmail-newu(8),
+qmail-lspawn(8)
diff --git a/qmail.7 b/qmail.7
new file mode 100644 (file)
index 0000000..3be2093
--- /dev/null
+++ b/qmail.7
@@ -0,0 +1,67 @@
+.TH qmail 7
+.SH "NAME"
+qmail \- overview of qmail documentation
+.SH "INTRODUCTION"
+.B qmail
+is a secure, reliable, efficient, simple message transfer agent.
+
+Users who want to control incoming messages
+should read
+.BR dot-qmail (5).
+Available commands for the
+.B .qmail
+file include
+.BR qbiff (1),
+.BR qlist (1),
+.BR qreceipt (1),
+.BR forward (1),
+and (for advanced users)
+.BR condredirect (1).
+Other helpful commands include
+.BR maildirmake (1),
+.BR maildir2mbox (1),
+and
+.BR maildirwatch (1).
+
+System administrators who want to control the entire
+.B qmail
+system should start with
+.BR qmail-control (5)
+and
+.BR qmail-start (8).
+There are three queue-monitoring tools:
+.BR qmail-qread (8),
+.BR qmail-qstat (8),
+and
+.BR qmail-tcpto (8).
+Incoming SMTP connections are handled by
+.BR qmail-smtpd (8).
+
+.B qmail
+offers two command-line message-sending interfaces:
+.BR qmail-inject (8)
+and
+.BR mailsubj (1).
+For background information on Internet mail messages,
+see
+.BR addresses (5),
+.BR envelopes (5),
+.BR qmail-header (5),
+and
+.BR forgeries (7).
+
+Miscellaneous documentation includes
+.BR qmail-upgrade (7),
+.BR qmail-limits (7),
+and
+.BR qmail-pop3d (8).
+
+This documentation describes version
+1.01
+of
+.BR qmail .
+See
+.B http://pobox.com/~djb/qmail.html
+for other
+.BR qmail -related
+software.
diff --git a/qmail.c b/qmail.c
new file mode 100644 (file)
index 0000000..7484331
--- /dev/null
+++ b/qmail.c
@@ -0,0 +1,103 @@
+#include "substdio.h"
+#include "readwrite.h"
+#include "wait.h"
+#include "exit.h"
+#include "fork.h"
+#include "fd.h"
+#include "qmail.h"
+#include "auto_qmail.h"
+
+static char *binqqargs[2] = { "bin/qmail-queue", 0 } ;
+
+int qmail_open(qq)
+struct qmail *qq;
+{
+  int pim[2];
+  int pie[2];
+
+  if (pipe(pim) == -1) return -1;
+  if (pipe(pie) == -1) { close(pim[0]); close(pim[1]); return -1; }
+  switch(qq->pid = vfork()) {
+    case -1:
+      close(pim[0]); close(pim[1]);
+      close(pie[0]); close(pie[1]);
+      return -1;
+    case 0:
+      close(pim[1]);
+      close(pie[1]);
+      if (fd_move(0,pim[0]) == -1) _exit(120);
+      if (fd_move(1,pie[0]) == -1) _exit(120);
+      if (chdir(auto_qmail) == -1) _exit(120);
+      execv(*binqqargs,binqqargs);
+      _exit(120);
+  }
+
+  qq->fdm = pim[1]; close(pim[0]);
+  qq->fde = pie[1]; close(pie[0]);
+  substdio_fdbuf(&qq->ss,write,qq->fdm,qq->buf,sizeof(qq->buf));
+  qq->flagerr = 0;
+  return 0;
+}
+
+unsigned long qmail_qp(qq) struct qmail *qq;
+{
+  return qq->pid;
+}
+
+void qmail_fail(qq) struct qmail *qq;
+{
+  qq->flagerr = 1;
+}
+
+void qmail_put(qq,s,len) struct qmail *qq; char *s; int len;
+{
+  if (!qq->flagerr) if (substdio_put(&qq->ss,s,len) == -1) qq->flagerr = 1;
+}
+
+void qmail_puts(qq,s) struct qmail *qq; char *s;
+{
+  if (!qq->flagerr) if (substdio_puts(&qq->ss,s) == -1) qq->flagerr = 1;
+}
+
+void qmail_from(qq,s) struct qmail *qq; char *s;
+{
+  if (substdio_flush(&qq->ss) == -1) qq->flagerr = 1;
+  close(qq->fdm);
+  substdio_fdbuf(&qq->ss,write,qq->fde,qq->buf,sizeof(qq->buf));
+  qmail_put(qq,"F",1);
+  qmail_puts(qq,s);
+  qmail_put(qq,"",1);
+}
+
+void qmail_to(qq,s) struct qmail *qq; char *s;
+{
+  qmail_put(qq,"T",1);
+  qmail_puts(qq,s);
+  qmail_put(qq,"",1);
+}
+
+int qmail_close(qq)
+struct qmail *qq;
+{
+  int wstat;
+
+  qmail_put(qq,"",1);
+  if (!qq->flagerr) if (substdio_flush(&qq->ss) == -1) qq->flagerr = 1;
+  close(qq->fde);
+
+  if (wait_pid(&wstat,qq->pid) != qq->pid) return QMAIL_WAITPID;
+  if (wait_crashed(wstat)) return QMAIL_CRASHED;
+  switch(wait_exitcode(wstat)) {
+    case 0: if (qq->flagerr) return QMAIL_BUG; return 0;
+    case 112: return QMAIL_USAGE;
+    case 115: return QMAIL_TOOLONG;
+    case 103: case 104: case 105: case 106: case 108: return QMAIL_SYS;
+    case 121: return QMAIL_READ;
+    case 122: return QMAIL_WRITE;
+    case 123: return QMAIL_NOMEM;
+    case 124: return QMAIL_TIMEOUT;
+    case 120: return QMAIL_EXECSOFT;
+    default: /* 101 or 102 */ return QMAIL_BUG;
+  }
+}
diff --git a/qmail.h b/qmail.h
new file mode 100644 (file)
index 0000000..7864ea1
--- /dev/null
+++ b/qmail.h
@@ -0,0 +1,36 @@
+#ifndef QMAIL_H
+#define QMAIL_H
+
+#include "substdio.h"
+
+struct qmail {
+  int flagerr;
+  unsigned long pid;
+  int fdm;
+  int fde;
+  substdio ss;
+  char buf[1024];
+} ;
+
+extern int qmail_open();
+extern void qmail_put();
+extern void qmail_puts();
+extern void qmail_from();
+extern void qmail_to();
+extern void qmail_fail();
+extern int qmail_close();
+extern unsigned long qmail_qp();
+
+#define QMAIL_WAITPID -2
+#define QMAIL_CRASHED -3
+#define QMAIL_USAGE -4
+#define QMAIL_BUG -5
+#define QMAIL_SYS -6
+#define QMAIL_READ -7
+#define QMAIL_WRITE -8
+#define QMAIL_NOMEM -9
+#define QMAIL_EXECSOFT -11
+#define QMAIL_TIMEOUT -13
+#define QMAIL_TOOLONG -14
+
+#endif
diff --git a/qreceipt.1 b/qreceipt.1
new file mode 100644 (file)
index 0000000..4d3cd08
--- /dev/null
@@ -0,0 +1,33 @@
+.TH qreceipt 1
+.SH NAME
+qreceipt \- respond to delivery notice requests
+.SH SYNOPSIS
+in
+.BR .qmail :
+.B |qreceipt
+.I youraddress
+.SH DESCRIPTION
+When a mail message arrives with 
+.I youraddress
+listed in a
+.B Notice-Requested-Upon-Delivery-To
+header field,
+.B qreceipt
+sends a success notice back to the envelope sender.
+
+.B WARNING:
+If you create a
+.B .qmail
+file to enable
+.BR qreceipt ,
+make sure to also add a line specifying delivery to your normal mailbox.
+For example:
+
+.EX
+     /home/joe/Mailbox
+.br
+     |qreceipt joe@nowhere.mil
+.EE
+.SH "SEE ALSO"
+dot-qmail(5),
+envelopes(5)
diff --git a/qreceipt.c b/qreceipt.c
new file mode 100644 (file)
index 0000000..9b51196
--- /dev/null
@@ -0,0 +1,131 @@
+#include "sig.h"
+#include "env.h"
+#include "substdio.h"
+#include "stralloc.h"
+#include "subfd.h"
+#include "getln.h"
+#include "alloc.h"
+#include "str.h"
+#include "hfield.h"
+#include "token822.h"
+#include "error.h"
+#include "gen_alloc.h"
+#include "gen_allocdefs.h"
+#include "headerbody.h"
+#include "exit.h"
+#include "open.h"
+#include "quote.h"
+#include "qmail.h"
+
+void die_noreceipt() { _exit(0); }
+void die() { _exit(100); }
+void die_temp() { _exit(111); }
+void die_nomem() {
+ substdio_putsflush(subfderr,"qreceipt: fatal: out of memory\n"); die_temp(); }
+void die_fork() {
+ substdio_putsflush(subfderr,"qreceipt: fatal: unable to fork\n"); die_temp(); }
+void die_qqperm() {
+ substdio_putsflush(subfderr,"qreceipt: fatal: permanent qmail-queue error\n"); die(); }
+void die_qqtemp() {
+ substdio_putsflush(subfderr,"qreceipt: fatal: temporary qmail-queue error\n"); die_temp(); }
+void die_usage() {
+ substdio_putsflush(subfderr,
+ "qreceipt: usage: qreceipt deliveryaddress\n"); die(); }
+void die_read() {
+ if (errno == error_nomem) die_nomem();
+ substdio_putsflush(subfderr,"qreceipt: fatal: read error\n"); die_temp(); }
+void doordie(sa,r) stralloc *sa; int r; {
+ if (r == 1) return; if (r == -1) die_nomem();
+ substdio_putsflush(subfderr,"qreceipt: fatal: unable to parse this: ");
+ substdio_putflush(subfderr,sa->s,sa->len); die(); }
+
+char *target;
+
+int flagreceipt = 0;
+
+char *returnpath;
+stralloc messageid = {0};
+stralloc sanotice = {0};
+
+int rwnotice(addr) token822_alloc *addr; { token822_reverse(addr);
+ if (token822_unquote(&sanotice,addr) != 1) die_nomem();
+ if (sanotice.len == str_len(target))
+   if (!str_diffn(sanotice.s,target,sanotice.len))
+     flagreceipt = 1;
+ token822_reverse(addr); return 1; }
+
+struct qmail qqt;
+
+stralloc quoted = {0};
+
+void finishheader()
+{
+ if (!flagreceipt) die_noreceipt();
+ if (str_equal(returnpath,"")) die_noreceipt();
+ if (str_equal(returnpath,"#@[]")) die_noreceipt();
+
+ if (!quote2(&quoted,returnpath)) die_nomem();
+
+ if (qmail_open(&qqt) == -1) die_fork();
+
+ qmail_puts(&qqt,"From: DELIVERY NOTICE SYSTEM <");
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,">\n");
+ qmail_puts(&qqt,"To: <");
+ qmail_put(&qqt,quoted.s,quoted.len);
+ qmail_puts(&qqt,">\n");
+ qmail_puts(&qqt,"Subject: success notice\n\
+\n\
+Hi! This is the qreceipt program. Your message was delivered to the\n\
+following address: ");
+ qmail_puts(&qqt,target);
+ qmail_puts(&qqt,". Thanks for asking.\n");
+ if (messageid.s)
+  {
+   qmail_puts(&qqt,"Your ");
+   qmail_put(&qqt,messageid.s,messageid.len);
+  }
+
+ qmail_from(&qqt,"");
+ qmail_to(&qqt,returnpath);
+
+ switch(qmail_close(&qqt))
+  {
+   case 0: break;
+   case QMAIL_TOOLONG: die_qqperm();
+   default: die_qqtemp();
+  }
+}
+
+stralloc hfbuf = {0};
+token822_alloc hfin = {0};
+token822_alloc hfrewrite = {0};
+token822_alloc hfaddr = {0};
+
+void doheaderfield(h)
+stralloc *h;
+{
+ switch(hfield_known(h->s,h->len))
+  {
+   case H_MESSAGEID:
+     if (!stralloc_copy(&messageid,h)) die_nomem();
+     break;
+   case H_NOTICEREQUESTEDUPONDELIVERYTO:
+     doordie(h,token822_parse(&hfin,h,&hfbuf));
+     doordie(h,token822_addrlist(&hfrewrite,&hfaddr,&hfin,rwnotice));
+     break;
+  }
+}
+
+void dobody(h) stralloc *h; { ; }
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ sig_pipeignore();
+ if (!(target = argv[1])) die_usage();
+ if (!(returnpath = env_get("SENDER"))) die_usage();
+ if (headerbody(subfdin,doheaderfield,finishheader,dobody) == -1) die_read();
+ die_noreceipt();
+}
diff --git a/qsmhook.c b/qsmhook.c
new file mode 100644 (file)
index 0000000..d5b38aa
--- /dev/null
+++ b/qsmhook.c
@@ -0,0 +1,137 @@
+#include "fd.h"
+#include "stralloc.h"
+#include "readwrite.h"
+#include "sgetopt.h"
+#include "wait.h"
+#include "env.h"
+#include "byte.h"
+#include "str.h"
+#include "alloc.h"
+#include "exit.h"
+#include "fork.h"
+#include "case.h"
+#include "subfd.h"
+#include "error.h"
+#include "substdio.h"
+#include "sig.h"
+
+void die(e,s) int e; char *s; { substdio_putsflush(subfderr,s); _exit(e); }
+void die_usage() { die(100,"qsmhook: fatal: incorrect usage\n"); }
+void die_temp() { die(111,"qsmhook: fatal: temporary problem\n"); }
+void die_read() { die(111,"qsmhook: fatal: unable to read message\n"); }
+void die_badcmd() { die(100,"qsmhook: fatal: command not found\n"); }
+
+int flagrpline = 0; char *rpline;
+int flagufline = 1; char *ufline;
+int flagdtline = 0; char *dtline;
+char *host;
+char *sender;
+char *recip;
+
+stralloc newarg = {0};
+
+substdio ssout;
+char outbuf[SUBSTDIO_OUTSIZE];
+substdio ssin;
+char inbuf[SUBSTDIO_INSIZE];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int pid;
+ int wstat;
+ int pi[2];
+ int opt;
+ char **arg;
+ char *x;
+ int i;
+ int flagesc;
+
+ sig_pipeignore();
+
+ if (!(dtline = env_get("DTLINE"))) die_usage();
+ if (!(rpline = env_get("RPLINE"))) die_usage();
+ if (!(ufline = env_get("UFLINE"))) die_usage();
+ if (!(recip = env_get("LOCAL"))) die_usage();
+ if (!(host = env_get("HOST"))) die_usage();
+ if (!(sender = env_get("SENDER"))) die_usage();
+
+ while ((opt = getopt(argc,argv,"DFlMmnPsx:")) != opteof)
+   switch(opt)
+    {
+     case 'D': case 'F': case 'M': break; /* be serious */
+     case 'l': flagdtline = 1; break; /* also return-receipt-to? blech */
+     case 'm': break; /* we only handle one recipient anyway */
+     case 'n': flagufline = 0; break;
+     case 's': break; /* could call quote() otherwise, i suppose... */
+     case 'P': flagrpline = 1; break;
+     case 'x':
+       if (case_starts(recip,optarg))
+        recip += str_len(optarg);
+       break;
+     default:
+       _exit(100);
+    }
+ argc -= optind;
+ argv += optind;
+
+ if (!*argv) die_usage();
+
+ for (arg = argv;x = *arg;++arg)
+  {
+   if (!stralloc_copys(&newarg,"")) die_temp();
+   flagesc = 0;
+   for (i = 0;x[i];++i)
+     if (flagesc)
+      {
+       switch(x[i])
+       {
+        case '%': if (!stralloc_cats(&newarg,"%")) die_temp(); break;
+        case 'g': if (!stralloc_cats(&newarg,sender)) die_temp(); break;
+        case 'h': if (!stralloc_cats(&newarg,host)) die_temp(); break;
+        case 'u': if (!stralloc_cats(&newarg,recip)) die_temp(); break;
+       }
+       flagesc = 0;
+      }
+     else
+       if (x[i] == '%')
+        flagesc = 1;
+       else
+        if (!stralloc_append(&newarg,&x[i])) die_temp();
+   if (!stralloc_0(&newarg)) die_temp();
+   i = str_len(newarg.s) + 1;
+   if (!(x = alloc(i))) die_temp();
+   byte_copy(x,i,newarg.s);
+   *arg = x;
+  }
+
+ if (pipe(pi) == -1) die_temp();
+
+ switch(pid = fork())
+  {
+   case -1:
+     die_temp();
+   case 0:
+     close(pi[1]);
+     if (fd_move(0,pi[0]) == -1) die_temp();
+     sig_pipedefault();
+     execvp(*argv,argv);
+     if (error_temp(errno)) die_temp();
+     die_badcmd();
+  }
+ close(pi[0]);
+
+ substdio_fdbuf(&ssout,write,pi[1],outbuf,sizeof(outbuf));
+ substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
+ if (flagufline) substdio_bputs(&ssout,ufline);
+ if (flagrpline) substdio_bputs(&ssout,rpline);
+ if (flagdtline) substdio_bputs(&ssout,dtline);
+ if (substdio_copy(&ssout,&ssin) == -2) die_read();
+ substdio_flush(&ssout);
+ close(pi[1]);
+
+ if (wait_pid(&wstat,pid) == -1) die_temp();
+ if (wait_crashed(wstat)) die_temp();
+ _exit(wait_exitcode(wstat));
+}
diff --git a/qsutil.c b/qsutil.c
new file mode 100644 (file)
index 0000000..80c619d
--- /dev/null
+++ b/qsutil.c
@@ -0,0 +1,46 @@
+#include "stralloc.h"
+#include "readwrite.h"
+#include "substdio.h"
+#include "qsutil.h"
+
+static stralloc foo = {0};
+
+static char errbuf[1];
+static struct substdio sserr = SUBSTDIO_FDBUF(write,0,errbuf,1);
+
+void logsa(sa) stralloc *sa; {
+ substdio_putflush(&sserr,sa->s,sa->len); }
+void log1(s1) char *s1; {
+ substdio_putsflush(&sserr,s1); }
+void log2(s1,s2) char *s1; char *s2; {
+ substdio_putsflush(&sserr,s1);
+ substdio_putsflush(&sserr,s2); }
+void log3(s1,s2,s3) char *s1; char *s2; char *s3; {
+ substdio_putsflush(&sserr,s1);
+ substdio_putsflush(&sserr,s2);
+ substdio_putsflush(&sserr,s3); }
+void nomem() { log1("alert: out of memory, sleeping...\n"); sleep(10); }
+
+void pausedir(dir) char *dir;
+{ log3("alert: unable to opendir ",dir,", sleeping...\n"); sleep(10); }
+
+static int issafe(ch) char ch;
+{
+ if (ch == '%') return 0; /* general principle: allman's code is crap */
+ if (ch < 33) return 0;
+ if (ch > 126) return 0;
+ return 1;
+}
+
+void logsafe(s) char *s;
+{
+ int i;
+ while (!stralloc_copys(&foo,s)) nomem();
+ for (i = 0;i < foo.len;++i)
+   if (foo.s[i] == '\n')
+     foo.s[i] = '/';
+   else
+     if (!issafe(foo.s[i]))
+       foo.s[i] = '_';
+ logsa(&foo);
+}
diff --git a/qsutil.h b/qsutil.h
new file mode 100644 (file)
index 0000000..a746845
--- /dev/null
+++ b/qsutil.h
@@ -0,0 +1,12 @@
+#ifndef QSUTIL_H
+#define QSUTIL_H
+
+extern void log1();
+extern void log2();
+extern void log3();
+extern void logsa();
+extern void nomem();
+extern void pausedir();
+extern void logsafe();
+
+#endif
diff --git a/quote.c b/quote.c
new file mode 100644 (file)
index 0000000..9f97d93
--- /dev/null
+++ b/quote.c
@@ -0,0 +1,82 @@
+#include "stralloc.h"
+#include "str.h"
+#include "quote.h"
+
+/*
+quote() encodes a box as per rfc 821 and rfc 822,
+while trying to do as little quoting as possible.
+no, 821 and 822 don't have the same encoding. they're not even close.
+no special encoding here for bytes above 127.
+*/
+
+static char ok[128] = {
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
+,0,7,0,7,7,7,7,7,0,0,7,7,0,7,7,7 ,7,7,7,7,7,7,7,7,7,7,0,0,0,7,0,7
+,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,0,0,0,7,7
+,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 ,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,0
+} ;
+
+static int doit(saout,sain)
+stralloc *saout;
+stralloc *sain;
+{
+ char ch;
+ int i;
+ int j;
+
+ if (!stralloc_ready(saout,sain->len * 2 + 2)) return 0;
+ j = 0;
+ saout->s[j++] = '"';
+ for (i = 0;i < sain->len;++i)
+  {
+   ch = sain->s[i];
+   if ((ch == '\r') || (ch == '\n') || (ch == '"') || (ch == '\\'))
+     saout->s[j++] = '\\';
+   saout->s[j++] = ch;
+  }
+ saout->s[j++] = '"';
+ saout->len = j;
+ return 1;
+}
+
+int quote_need(s,n)
+char *s;
+unsigned int n;
+{
+ unsigned char uch;
+ int i;
+ if (!n) return 0;
+ for (i = 0;i < n;++i)
+  {
+   uch = s[i];
+   if (uch >= 128) return 1;
+   if (!ok[uch]) return 1;
+  }
+ if (s[0] == '.') return 1;
+ if (s[n - 1] == '.') return 1;
+ for (i = 0;i < n - 1;++i) if (s[i] == '.') if (s[i + 1] == '.') return 1;
+ return 0;
+}
+
+int quote(saout,sain)
+stralloc *saout;
+stralloc *sain;
+{
+ if (quote_need(sain->s,sain->len)) return doit(saout,sain);
+ return stralloc_copy(saout,sain);
+}
+
+static stralloc foo = {0};
+
+int quote2(sa,s)
+stralloc *sa;
+char *s;
+{
+ int j;
+ j = str_rchr(s,'@');
+ if (!stralloc_copys(&foo,s)) return 0;
+ if (!s[j]) return quote(sa,&foo);
+ foo.len = j;
+ if (!quote(sa,&foo)) return 0;
+ return stralloc_cats(sa,s + j);
+}
diff --git a/quote.h b/quote.h
new file mode 100644 (file)
index 0000000..8ab7356
--- /dev/null
+++ b/quote.h
@@ -0,0 +1,8 @@
+#ifndef QUOTE_H
+#define QUOTE_H
+
+extern int quote_need();
+extern int quote();
+extern int quote2();
+
+#endif
diff --git a/readsubdir.c b/readsubdir.c
new file mode 100644 (file)
index 0000000..81aa241
--- /dev/null
@@ -0,0 +1,49 @@
+#include "readsubdir.h"
+#include "fmt.h"
+#include "scan.h"
+#include "str.h"
+#include "auto_split.h"
+
+void readsubdir_init(rs,name,pause)
+readsubdir *rs;
+char *name;
+void (*pause)();
+{
+ rs->name = name;
+ rs->pause = pause;
+ rs->dir = 0;
+ rs->pos = 0;
+}
+
+static char namepos[FMT_ULONG + 4 + READSUBDIR_NAMELEN];
+
+int readsubdir_next(rs,id)
+readsubdir *rs;
+unsigned long *id;
+{
+ direntry *d;
+ unsigned int len;
+
+ if (!rs->dir)
+  {
+   if (rs->pos >= auto_split) return 0;
+   if (str_len(rs->name) > READSUBDIR_NAMELEN) { rs->pos++; return -1; }
+   len = 0;
+   len += fmt_str(namepos + len,rs->name);
+   namepos[len++] = '/';
+   len += fmt_ulong(namepos + len,(unsigned long) rs->pos);
+   namepos[len] = 0;
+   while (!(rs->dir = opendir(namepos))) rs->pause(namepos);
+   rs->pos++;
+   return -1;
+  }
+
+ d = readdir(rs->dir);
+ if (!d) { closedir(rs->dir); rs->dir = 0; return -1; }
+
+ if (str_equal(d->d_name,".")) return -1;
+ if (str_equal(d->d_name,"..")) return -1;
+ len = scan_ulong(d->d_name,id);
+ if (!len || d->d_name[len]) return -2;
+ return 1;
+}
diff --git a/readsubdir.h b/readsubdir.h
new file mode 100644 (file)
index 0000000..353942f
--- /dev/null
@@ -0,0 +1,20 @@
+#ifndef READSUBDIR_H
+#define READSUBDIR_H
+
+#include "direntry.h"
+
+typedef struct readsubdir
+ {
+  DIR *dir;
+  int pos;
+  char *name;
+  void (*pause)();
+ }
+readsubdir;
+
+extern void readsubdir_init();
+extern int readsubdir_next();
+
+#define READSUBDIR_NAMELEN 10
+
+#endif
diff --git a/readwrite.h b/readwrite.h
new file mode 100644 (file)
index 0000000..2a64968
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef READWRITE_H
+#define READWRITE_H
+
+extern int read();
+extern int write();
+
+#endif
diff --git a/received.c b/received.c
new file mode 100644 (file)
index 0000000..07706d5
--- /dev/null
@@ -0,0 +1,71 @@
+#include "fmt.h"
+#include "qmail.h"
+#include "now.h"
+#include "datetime.h"
+#include "date822fmt.h"
+#include "received.h"
+
+static int issafe(ch) char ch;
+{
+  if (ch == '.') return 1;
+  if (ch == '@') return 1;
+  if (ch == '%') return 1;
+  if (ch == '+') return 1;
+  if (ch == '/') return 1;
+  if (ch == '=') return 1;
+  if (ch == ':') return 1;
+  if (ch == '-') return 1;
+  if ((ch >= 'a') && (ch <= 'z')) return 1;
+  if ((ch >= 'A') && (ch <= 'Z')) return 1;
+  if ((ch >= '0') && (ch <= '9')) return 1;
+  return 0;
+}
+
+void safeput(qqt,s)
+struct qmail *qqt;
+char *s;
+{
+  char ch;
+  while (ch = *s++) {
+    if (!issafe(ch)) ch = '?';
+    qmail_put(qqt,&ch,1);
+  }
+}
+
+static char buf[DATE822FMT];
+
+/* "Received: from relay1.uu.net (HELO uunet.uu.net) (7@192.48.96.5)\n" */
+/* "  by silverton.berkeley.edu with SMTP; 26 Sep 1995 04:46:54 -0000\n" */
+
+void received(qqt,protocol,local,remoteip,remotehost,remoteinfo,helo)
+struct qmail *qqt;
+char *protocol;
+char *local;
+char *remoteip;
+char *remotehost;
+char *remoteinfo;
+char *helo;
+{
+  struct datetime dt;
+
+  qmail_puts(qqt,"Received: from ");
+  safeput(qqt,remotehost);
+  if (helo) {
+    qmail_puts(qqt," (HELO ");
+    safeput(qqt,helo);
+    qmail_puts(qqt,")");
+  }
+  qmail_puts(qqt," (");
+  if (remoteinfo) {
+    safeput(qqt,remoteinfo);
+    qmail_puts(qqt,"@");
+  }
+  safeput(qqt,remoteip);
+  qmail_puts(qqt,")\n  by ");
+  safeput(qqt,local);
+  qmail_puts(qqt," with ");
+  qmail_puts(qqt,protocol);
+  qmail_puts(qqt,"; ");
+  datetime_tai(&dt,now());
+  qmail_put(qqt,buf,date822fmt(buf,&dt));
+}
diff --git a/received.h b/received.h
new file mode 100644 (file)
index 0000000..4e39dda
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef RECEIVED_H
+#define RECEIVED_H
+
+extern void received();
+
+#endif
diff --git a/remoteinfo.c b/remoteinfo.c
new file mode 100644 (file)
index 0000000..195eef6
--- /dev/null
@@ -0,0 +1,65 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <fcntl.h>
+#include "byte.h"
+#include "substdio.h"
+#include "ip.h"
+#include "fmt.h"
+#include "timeoutconn.h"
+#include "timeoutread.h"
+#include "timeoutwrite.h"
+#include "remoteinfo.h"
+
+static char line[999];
+
+char *remoteinfo_get(ipr,rp,ipl,lp,timeout)
+struct ip_address *ipr;
+unsigned long rp;
+struct ip_address *ipl;
+unsigned long lp;
+int timeout;
+{
+  char *x;
+  int s;
+  struct sockaddr_in sin;
+  substdio ss;
+  char buf[32];
+  unsigned int len;
+  int numcolons;
+  char ch;
+  s = socket(AF_INET,SOCK_STREAM,0);
+  if (s == -1) return 0;
+  byte_zero(&sin,sizeof(sin));
+  sin.sin_family = AF_INET;
+  byte_copy(&sin.sin_addr,4,ipl);
+  sin.sin_port = 0;
+  if (bind(s,(struct sockaddr *) &sin,sizeof(sin)) == -1) { close(s); return 0; }
+  if (timeoutconn(s,ipr,113,timeout) == -1) { close(s); return 0; }
+  fcntl(s,F_SETFL,fcntl(s,F_GETFL,0) & ~O_NDELAY);
+  len = 0;
+  len += fmt_ulong(line + len,rp);
+  len += fmt_str(line + len," , ");
+  len += fmt_ulong(line + len,lp);
+  len += fmt_str(line + len,"\r\n");
+  substdio_fdbuf(&ss,timeoutwrite,TIMEOUTWRITE(timeout,s),buf,sizeof(buf));
+  if (substdio_putflush(&ss,line,len) == -1) { close(s); return 0; }
+  substdio_fdbuf(&ss,timeoutread,TIMEOUTREAD(timeout,s),buf,sizeof(buf));
+  x = line;
+  numcolons = 0;
+  for (;;) {
+    if (substdio_get(&ss,&ch,1) != 1) { close(s); return 0; }
+    if ((ch == ' ') || (ch == '\t') || (ch == '\r')) continue;
+    if (ch == '\n') break;
+    if (numcolons < 3) { if (ch == ':') ++numcolons; }
+    else { *x++ = ch; if (x == line + sizeof(line) - 1) break; }
+  }
+  *x = 0;
+  close(s);
+  return line;
+}
diff --git a/remoteinfo.h b/remoteinfo.h
new file mode 100644 (file)
index 0000000..d5d9097
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef REMOTEINFO_H
+#define REMOTEINFO_H
+
+extern char *remoteinfo_get();
+
+#endif
diff --git a/scan.h b/scan.h
new file mode 100644 (file)
index 0000000..53ce703
--- /dev/null
+++ b/scan.h
@@ -0,0 +1,27 @@
+#ifndef SCAN_H
+#define SCAN_H
+
+extern unsigned int scan_uint();
+extern unsigned int scan_xint();
+extern unsigned int scan_nbbint();
+extern unsigned int scan_ushort();
+extern unsigned int scan_xshort();
+extern unsigned int scan_nbbshort();
+extern unsigned int scan_ulong();
+extern unsigned int scan_xlong();
+extern unsigned int scan_nbblong();
+
+extern unsigned int scan_plusminus();
+extern unsigned int scan_0x();
+
+extern unsigned int scan_whitenskip();
+extern unsigned int scan_nonwhitenskip();
+extern unsigned int scan_charsetnskip();
+extern unsigned int scan_noncharsetnskip();
+
+extern unsigned int scan_strncmp();
+extern unsigned int scan_memcmp();
+
+extern unsigned int scan_long();
+
+#endif
diff --git a/scan_8long.c b/scan_8long.c
new file mode 100644 (file)
index 0000000..8b3a6df
--- /dev/null
@@ -0,0 +1,11 @@
+#include "scan.h"
+
+unsigned int scan_8long(s,u) register char *s; register unsigned long *u;
+{
+  register unsigned int pos; register unsigned long result;
+  register unsigned long c;
+  pos = 0; result = 0;
+  while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 8)
+    { result = result * 8 + c; ++pos; }
+  *u = result; return pos;
+}
diff --git a/scan_nbblong.c b/scan_nbblong.c
new file mode 100644 (file)
index 0000000..5078053
--- /dev/null
@@ -0,0 +1,33 @@
+#include "scan.h"
+
+unsigned int scan_nbblong(s,n,base,bext,u)
+char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned long *u;
+/* Note that n == 0 means scan forever. Hopefully this is a good choice. */
+{
+  unsigned int pos; unsigned long result; unsigned long c;
+  pos = 0; result = 0;
+  while (((c = (unsigned long) (unsigned char) (s[pos] - '0')) < base)
+       ||(((c = (unsigned long) (unsigned char) (s[pos] - 'a')) < bext)
+        &&(c = c + base))
+       ||(((c = (unsigned long) (unsigned char) (s[pos] - 'A')) < bext)
+        &&(c = c + base))
+        ) /* this gets the job done */
+    { result = result * (base + bext) + c; ++pos; if (pos == n) break; }
+  *u = result; return pos;
+}
+
+unsigned int scan_nbbint(s,n,base,bext,u)
+char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned int *u;
+{
+  unsigned int pos; unsigned long result;
+  pos = scan_nbblong(s,n,base,bext,&result);
+  *u = result; return pos;
+}
+
+unsigned int scan_nbbshort(s,n,base,bext,u)
+char *s; unsigned int n; unsigned int base; unsigned int bext; unsigned short *u;
+{
+  unsigned int pos; unsigned long result;
+  pos = scan_nbblong(s,n,base,bext,&result);
+  *u = result; return pos;
+}
diff --git a/scan_ulong.c b/scan_ulong.c
new file mode 100644 (file)
index 0000000..27c41ea
--- /dev/null
@@ -0,0 +1,11 @@
+#include "scan.h"
+
+unsigned int scan_ulong(s,u) register char *s; register unsigned long *u;
+{
+  register unsigned int pos; register unsigned long result;
+  register unsigned long c;
+  pos = 0; result = 0;
+  while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 10)
+    { result = result * 10 + c; ++pos; }
+  *u = result; return pos;
+}
diff --git a/seek.h b/seek.h
new file mode 100644 (file)
index 0000000..964fba3
--- /dev/null
+++ b/seek.h
@@ -0,0 +1,15 @@
+#ifndef SEEK_H
+#define SEEK_H
+
+typedef unsigned long seek_pos;
+
+extern seek_pos seek_cur();
+
+extern int seek_set();
+extern int seek_end();
+
+extern int seek_trunc();
+
+#define seek_begin(fd) (seek_set((fd),(seek_pos) 0))
+
+#endif
diff --git a/seek_cur.c b/seek_cur.c
new file mode 100644 (file)
index 0000000..c8a3ee8
--- /dev/null
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include "seek.h"
+
+#define CUR 1 /* sigh */
+
+seek_pos seek_cur(fd) int fd;
+{ return lseek(fd,(off_t) 0,CUR); }
diff --git a/seek_end.c b/seek_end.c
new file mode 100644 (file)
index 0000000..8a7b3c5
--- /dev/null
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include "seek.h"
+
+#define END 2 /* sigh */
+
+int seek_end(fd) int fd;
+{ if (lseek(fd,(off_t) 0,END) == -1) return -1; return 0; }
diff --git a/seek_set.c b/seek_set.c
new file mode 100644 (file)
index 0000000..f540664
--- /dev/null
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include "seek.h"
+
+#define SET 0 /* sigh */
+
+int seek_set(fd,pos) int fd; seek_pos pos;
+{ if (lseek(fd,(off_t) pos,SET) == -1) return -1; return 0; }
diff --git a/seek_trunc.c b/seek_trunc.c
new file mode 100644 (file)
index 0000000..6a1a73e
--- /dev/null
@@ -0,0 +1,5 @@
+#include <sys/types.h>
+#include "seek.h"
+
+int seek_trunc(fd,pos) int fd; seek_pos pos;
+{ return ftruncate(fd,(off_t) pos); }
diff --git a/select.h1 b/select.h1
new file mode 100644 (file)
index 0000000..32d0968
--- /dev/null
+++ b/select.h1
@@ -0,0 +1,8 @@
+#ifndef SELECT_H
+#define SELECT_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+extern int select();
+
+#endif
diff --git a/select.h2 b/select.h2
new file mode 100644 (file)
index 0000000..eb4b8fe
--- /dev/null
+++ b/select.h2
@@ -0,0 +1,9 @@
+#ifndef SELECT_H
+#define SELECT_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h>
+extern int select();
+
+#endif
diff --git a/sendmail.c b/sendmail.c
new file mode 100644 (file)
index 0000000..f4205e1
--- /dev/null
@@ -0,0 +1,98 @@
+#include "sgetopt.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "alloc.h"
+#include "auto_qmail.h"
+#include "exit.h"
+#include "env.h"
+#include "str.h"
+
+void nomem()
+{
+ substdio_putsflush(subfderr,"sendmail: fatal: out of memory\n");
+ _exit(111);
+}
+
+int flagh;
+char *sender;
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int opt;
+ char **qiargv;
+ char **arg;
+ int i;
+
+ if (chdir(auto_qmail) == -1)
+  {
+   substdio_putsflush(subfderr,"sendmail: fatal: unable to switch to qmail home directory\n");
+   _exit(111);
+  }
+
+ flagh = 0;
+ sender = 0;
+ while ((opt = getopt(argc,argv,"vimte:f:p:o:B:F:EJx")) != opteof)
+   switch(opt)
+    {
+     case 'B': break;
+     case 't': flagh = 1; break;
+     case 'f': sender = optarg; break;
+     case 'F': if (!env_put2("MAILNAME",optarg)) nomem(); break;
+     case 'p': break; /* could generate a Received line from optarg */
+     case 'v': break;
+     case 'i': break; /* what an absurd concept */
+     case 'x': break; /* SVR4 stupidity */
+     case 'm': break; /* twisted-paper-path blindness, incompetent design */
+     case 'e': break; /* qmail has only one error mode */
+     case 'o':
+       switch(optarg[0])
+       {
+        case 'd': break; /* qmail has only one delivery mode */
+        case 'e': break; /* see 'e' above */
+        case 'i': break; /* see 'i' above */
+        case 'm': break; /* see 'm' above */
+       }
+       break;
+     case 'E': case 'J': /* Sony NEWS-OS */
+       while (argv[optind][optpos]) ++optpos; /* skip optional argument */
+       break;
+     default:
+       _exit(100);
+    }
+ argc -= optind;
+ argv += optind;
+
+ if (str_equal(optprogname,"mailq"))
+  {
+   substdio_putsflush(subfderr,"sendmail: fatal: please use qmail-qread instead\n");
+   _exit(100);
+  }
+
+ if (str_equal(optprogname,"newaliases"))
+  {
+   substdio_putsflush(subfderr,"sendmail: fatal: please use the qmsmac newaliases instead\n");
+   _exit(100);
+  }
+
+ qiargv = (char **) alloc((argc + 10) * sizeof(char *));
+ if (!qiargv) nomem();
+
+ arg = qiargv;
+ *arg++ = "bin/qmail-inject";
+ *arg++ = (flagh ? "-H" : "-a");
+ if (sender)
+  {
+   *arg++ = "-f";
+   *arg++ = sender;
+  }
+ *arg++ = "--";
+ for (i = 0;i < argc;++i) *arg++ = argv[i];
+ *arg = 0;
+
+ execv(*qiargv,qiargv);
+ substdio_putsflush(subfderr,"sendmail: fatal: unable to run qmail-inject\n");
+ _exit(111);
+}
diff --git a/sgetopt.3 b/sgetopt.3
new file mode 100644 (file)
index 0000000..bde0c2b
--- /dev/null
+++ b/sgetopt.3
@@ -0,0 +1,28 @@
+.TH sgetopt 3
+.SH NAME
+sgetopt \- get option character from command line
+.SH SYNTAX
+.B #include <sgetopt.h>
+.SH DESCRIPTION
+The
+.B sgetopt
+library is just like the
+.B getopt
+library,
+except that it prints errors using
+.B substdio
+rather than
+.BR stdio .
+
+See
+.B getopt(3)
+for interface details.
+.SH VERSION
+sgetopt version 1.9, 931201.
+.SH AUTHOR
+Placed into the public domain by Daniel J. Bernstein.
+.SH "SEE ALSO"
+getopt(3),
+subgetopt(3),
+subfd(3),
+substdio(3)
diff --git a/sgetopt.c b/sgetopt.c
new file mode 100644 (file)
index 0000000..a8bffc0
--- /dev/null
+++ b/sgetopt.c
@@ -0,0 +1,54 @@
+/* sgetopt.c, sgetopt.h: (yet another) improved getopt clone, outer layer
+D. J. Bernstein, djb@pobox.com.
+Depends on subgetopt.h, substdio.h, subfd.h.
+No system requirements.
+19970208: Cleanups.
+931201: Baseline.
+No known patent problems.
+
+Documentation in sgetopt.3.
+*/
+
+#include "substdio.h"
+#include "subfd.h"
+#define SGETOPTNOSHORT
+#include "sgetopt.h"
+#define SUBGETOPTNOSHORT
+#include "subgetopt.h"
+
+#define getopt sgetoptmine
+#define optind subgetoptind
+#define opterr sgetopterr
+#define optproblem subgetoptproblem
+#define optprogname sgetoptprogname
+
+int opterr = 1;
+char *optprogname = 0;
+
+int getopt(argc,argv,opts)
+int argc;
+char **argv;
+char *opts;
+{
+  int c;
+  char *s;
+
+  if (!optprogname) {
+    optprogname = *argv;
+    if (!optprogname) optprogname = "";
+    for (s = optprogname;*s;++s) if (*s == '/') optprogname = s + 1;
+  }
+  c = subgetopt(argc,argv,opts);
+  if (opterr)
+    if (c == '?') {
+      char chp[2]; chp[0] = optproblem; chp[1] = '\n';
+      substdio_puts(subfderr,optprogname);
+      if (argv[optind] && (optind < argc))
+        substdio_puts(subfderr,": illegal option -- ");
+      else
+        substdio_puts(subfderr,": option requires an argument -- ");
+      substdio_put(subfderr,chp,2);
+      substdio_flush(subfderr);
+    }
+  return c;
+}
diff --git a/sgetopt.h b/sgetopt.h
new file mode 100644 (file)
index 0000000..5f89127
--- /dev/null
+++ b/sgetopt.h
@@ -0,0 +1,21 @@
+#ifndef SGETOPT_H
+#define SGETOPT_H
+
+#ifndef SGETOPTNOSHORT
+#define getopt sgetoptmine
+#define optarg subgetoptarg
+#define optind subgetoptind
+#define optpos subgetoptpos
+#define opterr sgetopterr
+#define optproblem subgetoptproblem
+#define optprogname sgetoptprogname
+#define opteof subgetoptdone
+#endif
+
+#include "subgetopt.h"
+
+extern int sgetoptmine();
+extern int sgetopterr;
+extern char *sgetoptprogname;
+
+#endif
diff --git a/sig.h b/sig.h
new file mode 100644 (file)
index 0000000..9c3a28c
--- /dev/null
+++ b/sig.h
@@ -0,0 +1,43 @@
+#ifndef SIG_H
+#define SIG_H
+
+extern void sig_catch();
+extern void sig_block();
+extern void sig_unblock();
+extern void sig_blocknone();
+extern void sig_pause();
+
+extern void sig_dfl();
+
+extern void sig_miscignore();
+extern void sig_bugcatch();
+
+extern void sig_pipeignore();
+extern void sig_pipedefault();
+
+extern void sig_contblock();
+extern void sig_contunblock();
+extern void sig_contcatch();
+extern void sig_contdefault();
+
+extern void sig_termblock();
+extern void sig_termunblock();
+extern void sig_termcatch();
+extern void sig_termdefault();
+
+extern void sig_alarmblock();
+extern void sig_alarmunblock();
+extern void sig_alarmcatch();
+extern void sig_alarmdefault();
+
+extern void sig_childblock();
+extern void sig_childunblock();
+extern void sig_childcatch();
+extern void sig_childdefault();
+
+extern void sig_hangupblock();
+extern void sig_hangupunblock();
+extern void sig_hangupcatch();
+extern void sig_hangupdefault();
+
+#endif
diff --git a/sig_alarm.c b/sig_alarm.c
new file mode 100644 (file)
index 0000000..7092fdc
--- /dev/null
@@ -0,0 +1,7 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_alarmblock() { sig_block(SIGALRM); }
+void sig_alarmunblock() { sig_unblock(SIGALRM); }
+void sig_alarmcatch(f) void (*f)(); { sig_catch(SIGALRM,f); }
+void sig_alarmdefault() { sig_catch(SIGALRM,SIG_DFL); }
diff --git a/sig_block.c b/sig_block.c
new file mode 100644 (file)
index 0000000..c6b096a
--- /dev/null
@@ -0,0 +1,40 @@
+#include <signal.h>
+#include "sig.h"
+#include "hassgprm.h"
+
+void sig_block(sig)
+int sig;
+{
+#ifdef HASSIGPROCMASK
+  sigset_t ss;
+  sigemptyset(&ss);
+  sigaddset(&ss,sig);
+  sigprocmask(SIG_BLOCK,&ss,(sigset_t *) 0);
+#else
+  sigblock(1 << (sig - 1));
+#endif
+}
+
+void sig_unblock(sig)
+int sig;
+{
+#ifdef HASSIGPROCMASK
+  sigset_t ss;
+  sigemptyset(&ss);
+  sigaddset(&ss,sig);
+  sigprocmask(SIG_UNBLOCK,&ss,(sigset_t *) 0);
+#else
+  sigsetmask(sigsetmask(~0) & ~(1 << (sig - 1)));
+#endif
+}
+
+void sig_blocknone()
+{
+#ifdef HASSIGPROCMASK
+  sigset_t ss;
+  sigemptyset(&ss);
+  sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0);
+#else
+  sigsetmask(0);
+#endif
+}
diff --git a/sig_bug.c b/sig_bug.c
new file mode 100644 (file)
index 0000000..ae09c07
--- /dev/null
+++ b/sig_bug.c
@@ -0,0 +1,17 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_bugcatch(f) void (*f)();
+{
+  sig_catch(SIGILL,f);
+  sig_catch(SIGABRT,f);
+  sig_catch(SIGFPE,f);
+  sig_catch(SIGBUS,f);
+  sig_catch(SIGSEGV,f);
+#ifdef SIGSYS
+  sig_catch(SIGSYS,f);
+#endif
+#ifdef SIGEMT
+  sig_catch(SIGEMT,f);
+#endif
+}
diff --git a/sig_catch.c b/sig_catch.c
new file mode 100644 (file)
index 0000000..1888765
--- /dev/null
@@ -0,0 +1,18 @@
+#include <signal.h>
+#include "sig.h"
+#include "hassgact.h"
+
+void sig_catch(sig,f)
+int sig;
+void (*f)();
+{
+#ifdef HASSIGACTION
+  struct sigaction sa;
+  sa.sa_handler = f;
+  sa.sa_flags = 0;
+  sigemptyset(&sa.sa_mask);
+  sigaction(sig,&sa,(struct sigaction *) 0);
+#else
+  signal(sig,f); /* won't work under System V, even nowadays---dorks */
+#endif
+}
diff --git a/sig_child.c b/sig_child.c
new file mode 100644 (file)
index 0000000..fd5b39b
--- /dev/null
@@ -0,0 +1,7 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_childblock() { sig_block(SIGCHLD); }
+void sig_childunblock() { sig_unblock(SIGCHLD); }
+void sig_childcatch(f) void (*f)(); { sig_catch(SIGCHLD,f); }
+void sig_childdefault() { sig_catch(SIGCHLD,SIG_DFL); }
diff --git a/sig_hup.c b/sig_hup.c
new file mode 100644 (file)
index 0000000..4beb87f
--- /dev/null
+++ b/sig_hup.c
@@ -0,0 +1,7 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_hangupblock() { sig_block(SIGHUP); }
+void sig_hangupunblock() { sig_unblock(SIGHUP); }
+void sig_hangupcatch(f) void (*f)(); { sig_catch(SIGHUP,f); }
+void sig_hangupdefault() { sig_catch(SIGHUP,SIG_DFL); }
diff --git a/sig_misc.c b/sig_misc.c
new file mode 100644 (file)
index 0000000..287f6cb
--- /dev/null
@@ -0,0 +1,17 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_miscignore()
+{
+  sig_catch(SIGVTALRM,SIG_IGN);
+  sig_catch(SIGPROF,SIG_IGN);
+  sig_catch(SIGQUIT,SIG_IGN);
+  sig_catch(SIGINT,SIG_IGN);
+  sig_catch(SIGHUP,SIG_IGN);
+#ifdef SIGXCPU
+  sig_catch(SIGXCPU,SIG_IGN);
+#endif
+#ifdef SIGXFSZ
+  sig_catch(SIGXFSZ,SIG_IGN);
+#endif
+}
diff --git a/sig_pause.c b/sig_pause.c
new file mode 100644 (file)
index 0000000..3416734
--- /dev/null
@@ -0,0 +1,14 @@
+#include <signal.h>
+#include "sig.h"
+#include "hassgprm.h"
+
+void sig_pause()
+{
+#ifdef HASSIGPROCMASK
+  sigset_t ss;
+  sigemptyset(&ss);
+  sigsuspend(&ss);
+#else
+  sigpause(0);
+#endif
+}
diff --git a/sig_pipe.c b/sig_pipe.c
new file mode 100644 (file)
index 0000000..594ae7d
--- /dev/null
@@ -0,0 +1,5 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_pipeignore() { sig_catch(SIGPIPE,SIG_IGN); }
+void sig_pipedefault() { sig_catch(SIGPIPE,SIG_DFL); }
diff --git a/sig_term.c b/sig_term.c
new file mode 100644 (file)
index 0000000..ca72cc3
--- /dev/null
@@ -0,0 +1,7 @@
+#include <signal.h>
+#include "sig.h"
+
+void sig_termblock() { sig_block(SIGTERM); }
+void sig_termunblock() { sig_unblock(SIGTERM); }
+void sig_termcatch(f) void (*f)(); { sig_catch(SIGTERM,f); }
+void sig_termdefault() { sig_catch(SIGTERM,SIG_DFL); }
diff --git a/slurpclose.c b/slurpclose.c
new file mode 100644 (file)
index 0000000..27be0ea
--- /dev/null
@@ -0,0 +1,17 @@
+#include "stralloc.h"
+#include "readwrite.h"
+#include "slurpclose.h"
+
+int slurpclose(fd,sa,bufsize)
+int fd;
+stralloc *sa;
+int bufsize;
+{
+  int r;
+  for (;;) {
+    if (!stralloc_readyplus(sa,bufsize)) { close(fd); return -1; }
+    r = read(fd,sa->s + sa->len,bufsize);
+    if (r <= 0) { close(fd); return r; }
+    sa->len += r;
+  }
+}
diff --git a/slurpclose.h b/slurpclose.h
new file mode 100644 (file)
index 0000000..57e9eec
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef SLURPCLOSE_H
+#define SLURPCLOSE_H
+
+extern int slurpclose();
+
+#endif
diff --git a/spawn.c b/spawn.c
new file mode 100644 (file)
index 0000000..b2aee78
--- /dev/null
+++ b/spawn.c
@@ -0,0 +1,259 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+#include "sig.h"
+#include "wait.h"
+#include "substdio.h"
+#include "byte.h"
+#include "str.h"
+#include "stralloc.h"
+#include "select.h"
+#include "exit.h"
+#include "coe.h"
+#include "open.h"
+#include "error.h"
+#include "auto_qmail.h"
+#include "auto_uids.h"
+#include "auto_spawn.h"
+
+extern int truncreport;
+extern int spawn();
+extern void report();
+extern void initialize();
+
+struct delivery
+ {
+  int used;
+  int fdin; /* pipe input */
+  int pid; /* zero if child is dead */
+  int wstat; /* if !pid: status of child */
+  int fdout; /* pipe output, -1 if !pid; delays eof until after death */
+  stralloc output;
+ }
+;
+
+struct delivery *d;
+
+void sigchld()
+{
+ int wstat;
+ int pid;
+ int i;
+ while ((pid = wait_nohang(&wstat)) > 0)
+   for (i = 0;i < auto_spawn;++i) if (d[i].used)
+     if (d[i].pid == pid)
+      {
+       close(d[i].fdout); d[i].fdout = -1;
+       d[i].wstat = wstat; d[i].pid = 0;
+      }
+}
+
+int flagwriting = 1;
+
+int okwrite(fd,buf,n) int fd; char *buf; int n;
+{
+ int w;
+ if (!flagwriting) return n;
+ w = write(fd,buf,n);
+ if (w != -1) return w;
+ if (errno == error_intr) return -1;
+ flagwriting = 0; close(fd);
+ return n;
+}
+
+int flagreading = 1;
+char outbuf[1024]; substdio ssout;
+
+int stage = 0; /* reading 0:delnum 1:messid 2:sender 3:recip */
+int flagabort = 0; /* if 1, everything except delnum is garbage */
+int delnum;
+stralloc messid = {0};
+stralloc sender = {0};
+stralloc recip = {0};
+
+void err(s) char *s;
+{
+ char ch; ch = delnum; substdio_put(&ssout,&ch,1);
+ substdio_puts(&ssout,s); substdio_putflush(&ssout,"",1);
+}
+
+void docmd()
+{
+ int f;
+ int i;
+ int j;
+ int fdmess;
+ int pi[2];
+ struct stat st;
+
+ if (flagabort) { err("Zqmail-spawn out of memory. (#4.3.0)\n"); return; }
+ if (delnum < 0) { err("ZInternal error: delnum negative. (#4.3.5)\n"); return; }
+ if (delnum >= auto_spawn) { err("ZInternal error: delnum too big. (#4.3.5)\n"); return; }
+ if (d[delnum].used) { err("ZInternal error: delnum in use. (#4.3.5)\n"); return; }
+ for (i = 0;i < messid.len;++i)
+   if (messid.s[i])
+     if (!i || (messid.s[i] != '/'))
+       if ((unsigned char) (messid.s[i] - '0') > 9)
+        { err("DInternal error: messid has nonnumerics. (#5.3.5)\n"); return; }
+ if (messid.len > 100) { err("DInternal error: messid too long. (#5.3.5)\n"); return; }
+ if (!messid.s[0]) { err("DInternal error: messid too short. (#5.3.5)\n"); return; }
+
+ if (!stralloc_copys(&d[delnum].output,""))
+  { err("Zqmail-spawn out of memory. (#4.3.0)\n"); return; }
+
+ j = byte_rchr(recip.s,recip.len,'@');
+ if (j >= recip.len) { err("DSorry, address must include host name. (#5.1.3)\n"); return; }
+
+ fdmess = open_read(messid.s);
+ if (fdmess == -1) { err("Zqmail-spawn unable to open message. (#4.3.0)\n"); return; }
+
+ if (fstat(fdmess,&st) == -1)
+  { close(fdmess); err("Zqmail-spawn unable to fstat message. (#4.3.0)\n"); return; }
+ if ((st.st_mode & S_IFMT) != S_IFREG)
+  { close(fdmess); err("ZSorry, message has wrong type. (#4.3.5)\n"); return; }
+ if (st.st_uid != auto_uidq) /* aaack! qmailq has to be trusted! */
+  /* your security is already toast at this point. damage control... */
+  { close(fdmess); err("ZSorry, message has wrong owner. (#4.3.5)\n"); return; }
+
+ if (pipe(pi) == -1)
+  { close(fdmess); err("Zqmail-spawn unable to create pipe. (#4.3.0)\n"); return; }
+
+ coe(pi[0]);
+
+ f = spawn(fdmess,pi[1],sender.s,recip.s,j);
+ close(fdmess);
+ if (f == -1)
+  { close(pi[0]); close(pi[1]); err("Zqmail-spawn unable to fork. (#4.3.0)\n"); return; }
+
+ d[delnum].fdin = pi[0];
+ d[delnum].fdout = pi[1]; coe(pi[1]);
+ d[delnum].pid = f;
+ d[delnum].used = 1;
+}
+
+char cmdbuf[1024];
+
+void getcmd()
+{
+ int i;
+ int r;
+ char ch;
+
+ r = read(0,cmdbuf,sizeof(cmdbuf));
+ if (r == 0)
+  { flagreading = 0; return; }
+ if (r == -1)
+  {
+   if (errno != error_intr)
+     flagreading = 0;
+   return;
+  }
+ for (i = 0;i < r;++i)
+  {
+   ch = cmdbuf[i];
+   switch(stage)
+    {
+     case 0:
+       delnum = (unsigned int) (unsigned char) ch;
+       messid.len = 0; stage = 1; break;
+     case 1:
+       if (!stralloc_append(&messid,&ch)) flagabort = 1;
+       if (ch) break;
+       sender.len = 0; stage = 2; break;
+     case 2:
+       if (!stralloc_append(&sender,&ch)) flagabort = 1;
+       if (ch) break;
+       recip.len = 0; stage = 3; break;
+     case 3:
+       if (!stralloc_append(&recip,&ch)) flagabort = 1;
+       if (ch) break;
+       docmd();
+       flagabort = 0; stage = 0; break;
+    }
+  }
+}
+
+char inbuf[128];
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ char ch;
+ int i;
+ int r;
+ fd_set rfds;
+ int nfds;
+
+ if (chdir(auto_qmail) == -1) _exit(111);
+ if (chdir("queue/mess") == -1) _exit(111);
+ if (!stralloc_copys(&messid,"")) _exit(111);
+ if (!stralloc_copys(&sender,"")) _exit(111);
+ if (!stralloc_copys(&recip,"")) _exit(111);
+
+ d = (struct delivery *) alloc((auto_spawn + 10) * sizeof(struct delivery));
+ if (!d) _exit(111);
+
+ substdio_fdbuf(&ssout,okwrite,1,outbuf,sizeof(outbuf));
+
+ sig_pipeignore();
+ sig_childcatch(sigchld);
+
+ initialize(argc,argv);
+
+ ch = auto_spawn; substdio_putflush(&ssout,&ch,1);
+
+ for (i = 0;i < auto_spawn;++i) { d[i].used = 0; d[i].output.s = 0; }
+
+ for (;;)
+  {
+   if (!flagreading)
+    {
+     for (i = 0;i < auto_spawn;++i) if (d[i].used) break;
+     if (i >= auto_spawn) _exit(0);
+    }
+   sig_childunblock();
+
+   FD_ZERO(&rfds);
+   if (flagreading) FD_SET(0,&rfds);
+   nfds = 1;
+   for (i = 0;i < auto_spawn;++i) if (d[i].used)
+    { FD_SET(d[i].fdin,&rfds); if (d[i].fdin >= nfds) nfds = d[i].fdin + 1; }
+
+   r = select(nfds,&rfds,(fd_set *) 0,(fd_set *) 0,(struct timeval *) 0);
+   sig_childblock();
+
+   if (r != -1)
+    {
+     if (flagreading)
+       if (FD_ISSET(0,&rfds))
+        getcmd();
+     for (i = 0;i < auto_spawn;++i) if (d[i].used)
+       if (FD_ISSET(d[i].fdin,&rfds))
+       {
+        r = read(d[i].fdin,inbuf,128);
+        if (r == -1)
+          continue; /* read error on a readable pipe? be serious */
+        if (r == 0)
+         {
+           ch = i; substdio_put(&ssout,&ch,1);
+          report(&ssout,d[i].wstat,d[i].output.s,d[i].output.len);
+          substdio_put(&ssout,"",1);
+          substdio_flush(&ssout);
+          close(d[i].fdin); d[i].used = 0;
+          continue;
+         }
+        while (!stralloc_readyplus(&d[i].output,r)) sleep(10); /*XXX*/
+        byte_copy(d[i].output.s + d[i].output.len,r,inbuf);
+        d[i].output.len += r;
+        if (truncreport > 100)
+          if (d[i].output.len > truncreport)
+           {
+            char *truncmess = "\nError report too long, sorry.\n";
+            d[i].output.len = truncreport - str_len(truncmess) - 3;
+            stralloc_cats(&d[i].output,truncmess);
+           }
+       }
+    }
+  }
+}
diff --git a/splogger.8 b/splogger.8
new file mode 100644 (file)
index 0000000..15096b4
--- /dev/null
@@ -0,0 +1,60 @@
+.TH splogger 8
+.SH NAME
+splogger \- make entries in syslog
+.SH SYNOPSIS
+.B splogger
+[
+.I tag
+[
+.I fac
+]
+]
+.SH DESCRIPTION
+.B splogger
+reads a series of messages and feeds them to
+.BR syslog .
+At the front of each message it puts
+.I tag
+(default:
+.BR splogger )
+and a numerical timestamp.
+
+.B splogger
+checks for
+.B alert:
+or
+.B warning:
+at the beginning of each message.
+It selects a priority of
+LOG_ALERT, LOG_WARNING, or LOG_INFO accordingly.
+
+.B splogger
+logs messages with facility
+.IR fac .
+.I fac
+(default: 2)
+must be numeric.
+
+.B splogger
+converts unprintable characters to question marks.
+
+.B splogger
+does not log blank lines.
+
+.B splogger
+folds messages after 800 characters,
+since
+.B syslog
+can't handle long messages.
+.B splogger
+uses a + after the timestamp
+to mark folded lines.
+
+Note that the
+.B syslog
+mechanism is inherently unreliable:
+it does not guarantee that messages will be logged.
+It is also very slow.
+.SH "SEE ALSO"
+syslog(3),
+logger(8)
diff --git a/splogger.c b/splogger.c
new file mode 100644 (file)
index 0000000..fc49a33
--- /dev/null
@@ -0,0 +1,72 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <syslog.h>
+#include "error.h"
+#include "substdio.h"
+#include "subfd.h"
+#include "exit.h"
+#include "str.h"
+#include "scan.h"
+#include "fmt.h"
+
+char buf[800]; /* syslog truncates long lines (or crashes); GPACIC */
+int bufpos = 0; /* 0 <= bufpos < sizeof(buf) */
+int flagcont = 0;
+int priority; /* defined if flagcont */
+char stamp[FMT_ULONG + FMT_ULONG + 3]; /* defined if flagcont */
+
+void stamp_make()
+{
+  struct timeval tv;
+  char *s;
+  gettimeofday(&tv,(struct timezone *) 0);
+  s = stamp;
+  s += fmt_ulong(s,(unsigned long) tv.tv_sec);
+  *s++ = '.';
+  s += fmt_uint0(s,(unsigned int) tv.tv_usec,6);
+  *s = 0;
+}
+
+void flush()
+{
+  if (bufpos) {
+    buf[bufpos] = 0;
+    if (flagcont)
+      syslog(priority,"%s+%s",stamp,buf); /* logger folds invisibly; GPACIC */
+    else {
+      stamp_make();
+      priority = LOG_INFO;
+      if (str_start(buf,"warning:")) priority = LOG_WARNING;
+      if (str_start(buf,"alert:")) priority = LOG_ALERT;
+      syslog(priority,"%s %s",stamp,buf);
+      flagcont = 1;
+    }
+  }
+  bufpos = 0;
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+  char ch;
+
+  if (argv[1])
+    if (argv[2]) {
+      unsigned long facility;
+      scan_ulong(argv[2],&facility);
+      openlog(argv[1],0,facility << 3);
+    }
+    else
+      openlog(argv[1],0,LOG_MAIL);
+  else
+    openlog("splogger",0,LOG_MAIL);
+
+  for (;;) {
+    if (substdio_get(subfdin,&ch,1) < 1) _exit(0);
+    if (ch == '\n') { flush(); flagcont = 0; continue; }
+    if (bufpos == sizeof(buf) - 1) flush();
+    if ((ch < 32) || (ch > 126)) ch = '?'; /* logger truncates at 0; GPACIC */
+    buf[bufpos++] = ch;
+  }
+}
diff --git a/str.h b/str.h
new file mode 100644 (file)
index 0000000..e00773c
--- /dev/null
+++ b/str.h
@@ -0,0 +1,14 @@
+#ifndef STR_H
+#define STR_H
+
+extern unsigned int str_copy();
+extern int str_diff();
+extern int str_diffn();
+extern unsigned int str_len();
+extern unsigned int str_chr();
+extern unsigned int str_rchr();
+extern int str_start();
+
+#define str_equal(s,t) (!str_diff((s),(t)))
+
+#endif
diff --git a/str_chr.c b/str_chr.c
new file mode 100644 (file)
index 0000000..3691826
--- /dev/null
+++ b/str_chr.c
@@ -0,0 +1,19 @@
+#include "str.h"
+
+unsigned int str_chr(s,c)
+register char *s;
+int c;
+{
+  register char ch;
+  register char *t;
+
+  ch = c;
+  t = s;
+  for (;;) {
+    if (!*t) break; if (*t == ch) break; ++t;
+    if (!*t) break; if (*t == ch) break; ++t;
+    if (!*t) break; if (*t == ch) break; ++t;
+    if (!*t) break; if (*t == ch) break; ++t;
+  }
+  return t - s;
+}
diff --git a/str_cpy.c b/str_cpy.c
new file mode 100644 (file)
index 0000000..453d790
--- /dev/null
+++ b/str_cpy.c
@@ -0,0 +1,16 @@
+#include "str.h"
+
+unsigned int str_copy(s,t)
+register char *s;
+register char *t;
+{
+  register int len;
+
+  len = 0;
+  for (;;) {
+    if (!(*s = *t)) return len; ++s; ++t; ++len;
+    if (!(*s = *t)) return len; ++s; ++t; ++len;
+    if (!(*s = *t)) return len; ++s; ++t; ++len;
+    if (!(*s = *t)) return len; ++s; ++t; ++len;
+  }
+}
diff --git a/str_diff.c b/str_diff.c
new file mode 100644 (file)
index 0000000..18f8927
--- /dev/null
@@ -0,0 +1,17 @@
+#include "str.h"
+
+int str_diff(s,t)
+register char *s;
+register char *t;
+{
+  register char x;
+
+  for (;;) {
+    x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+  }
+  return ((int)(unsigned int)(unsigned char) x)
+       - ((int)(unsigned int)(unsigned char) *t);
+}
diff --git a/str_diffn.c b/str_diffn.c
new file mode 100644 (file)
index 0000000..89142f1
--- /dev/null
@@ -0,0 +1,18 @@
+#include "str.h"
+
+int str_diffn(s,t,len)
+register char *s;
+register char *t;
+unsigned int len;
+{
+  register char x;
+
+  for (;;) {
+    if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+    if (!len--) return 0; x = *s; if (x != *t) break; if (!x) break; ++s; ++t;
+  }
+  return ((int)(unsigned int)(unsigned char) x)
+       - ((int)(unsigned int)(unsigned char) *t);
+}
diff --git a/str_len.c b/str_len.c
new file mode 100644 (file)
index 0000000..2d2f88b
--- /dev/null
+++ b/str_len.c
@@ -0,0 +1,15 @@
+#include "str.h"
+
+unsigned int str_len(s)
+register char *s;
+{
+  register char *t;
+
+  t = s;
+  for (;;) {
+    if (!*t) return t - s; ++t;
+    if (!*t) return t - s; ++t;
+    if (!*t) return t - s; ++t;
+    if (!*t) return t - s; ++t;
+  }
+}
diff --git a/str_rchr.c b/str_rchr.c
new file mode 100644 (file)
index 0000000..1bf19d3
--- /dev/null
@@ -0,0 +1,22 @@
+#include "str.h"
+
+unsigned int str_rchr(s,c)
+register char *s;
+int c;
+{
+  register char ch;
+  register char *t;
+  register char *u;
+
+  ch = c;
+  t = s;
+  u = 0;
+  for (;;) {
+    if (!*t) break; if (*t == ch) u = t; ++t;
+    if (!*t) break; if (*t == ch) u = t; ++t;
+    if (!*t) break; if (*t == ch) u = t; ++t;
+    if (!*t) break; if (*t == ch) u = t; ++t;
+  }
+  if (!u) u = t;
+  return u - s;
+}
diff --git a/str_start.c b/str_start.c
new file mode 100644 (file)
index 0000000..2750af8
--- /dev/null
@@ -0,0 +1,15 @@
+#include "str.h"
+
+int str_start(s,t)
+register char *s;
+register char *t;
+{
+  register char x;
+
+  for (;;) {
+    x = *t++; if (!x) return 1; if (x != *s++) return 0;
+    x = *t++; if (!x) return 1; if (x != *s++) return 0;
+    x = *t++; if (!x) return 1; if (x != *s++) return 0;
+    x = *t++; if (!x) return 1; if (x != *s++) return 0;
+  }
+}
diff --git a/stralloc.3 b/stralloc.3
new file mode 100644 (file)
index 0000000..3123521
--- /dev/null
@@ -0,0 +1,160 @@
+.TH stralloc 3
+.SH NAME
+stralloc \- dynamically allocated strings
+.SH SYNTAX
+.B #include <stralloc.h>
+
+int \fBstralloc_ready\fP(&\fIsa\fR,\fIlen\fR);
+.br
+int \fBstralloc_readyplus\fP(&\fIsa\fR,\fIlen\fR);
+
+int \fBstralloc_copy\fP(&\fIsa\fR,&\fIsa2\fR);
+.br
+int \fBstralloc_copys\fP(&\fIsa\fR,\fIbuf\fR);
+.br
+int \fBstralloc_copyb\fP(&\fIsa\fR,\fIbuf\fR,\fIlen\fR);
+
+int \fBstralloc_cat\fP(&\fIsa\fR,&\fIsa2\fR);
+.br
+int \fBstralloc_cats\fP(&\fIsa\fR,\fIbuf\fR);
+.br
+int \fBstralloc_catb\fP(&\fIsa\fR,\fIbuf\fR,\fIlen\fR);
+
+int \fBstralloc_append\fP(&\fIsa\fR,\fIbuf\fR);
+.br
+int \fBstralloc_0\fP(&\fIsa\fR);
+
+int \fBstralloc_starts\fP(&\fIsa\fR,\fIbuf\fR);
+
+stralloc \fIsa\fR = {0};
+.br
+stralloc \fIsa2\fR = {0};
+.br
+unsigned int \fIlen\fR;
+.br
+char *\fIbuf\fR;
+.SH DESCRIPTION
+A
+.B stralloc
+variable holds a string in dynamically allocated space.
+String length is limited only by memory.
+String contents are unrestricted.
+
+The
+.B stralloc
+structure has three components:
+.I sa\fB.s
+is a pointer to the string, or 0 if it is not allocated;
+.I sa\fB.len
+is the number of bytes in the string, if it is allocated;
+.I sa\fB.a
+is the number of bytes allocated for the string, if it is allocated.
+A
+.B stralloc
+variable should be initialized to {0},
+meaning unallocated.
+
+.B stralloc_ready
+makes sure that
+.I sa
+has enough space allocated for
+.I len
+characters.
+It allocates extra space if necessary.
+
+.B stralloc_readyplus
+makes sure that
+.I sa
+has enough space allocated for
+.I len
+characters more than its current length.
+If
+.I sa
+is unallocated,
+.B stralloc_readyplus
+is the same as
+.BR stralloc_ready .
+
+.B stralloc_copy
+copies
+.I sa2
+to
+.IR sa ,
+allocating space if necessary.
+Here
+.I sa2
+is an allocated
+.B stralloc
+variable.
+
+.B stralloc_copys
+copies a 0-terminated string,
+.IR buf ,
+to
+.IR sa ,
+without the 0.
+
+.B stralloc_copyb
+copies
+.I len
+characters from
+.I buf
+to
+.IR sa .
+
+.B stralloc_cat
+appends
+.I sa2
+to
+.IR sa ,
+allocating space if necessary.
+If
+.I sa
+is unallocated,
+.B stralloc_cat
+is the same as
+.BR stralloc_copy .
+
+.B stralloc_cats
+and
+.B stralloc_catb
+are analogous to
+.B stralloc_copys
+and
+.BR stralloc_copyb .
+
+.B stralloc_append
+adds a single character,
+.IR *buf ,
+to
+.IR sa ,
+allocating space if necessary.
+
+.B stralloc_0
+adds a single 0 character
+to
+.IR sa .
+
+.B stralloc_starts
+returns 1 if the 0-terminated string
+.IR buf ,
+without the 0,
+is a prefix of
+.IR sa .
+.SH "ERROR HANDLING"
+If a
+.B stralloc
+routine runs out of memory,
+it leaves
+.I sa
+alone and returns 0,
+setting
+.B errno
+appropriately.
+On success it returns 1;
+this guarantees that
+.I sa
+is allocated.
+.SH "SEE ALSO"
+alloc(3),
+error(3)
diff --git a/stralloc.h b/stralloc.h
new file mode 100644 (file)
index 0000000..fca496c
--- /dev/null
@@ -0,0 +1,21 @@
+#ifndef STRALLOC_H
+#define STRALLOC_H
+
+#include "gen_alloc.h"
+
+GEN_ALLOC_typedef(stralloc,char,s,len,a)
+
+extern int stralloc_ready();
+extern int stralloc_readyplus();
+extern int stralloc_copy();
+extern int stralloc_cat();
+extern int stralloc_copys();
+extern int stralloc_cats();
+extern int stralloc_copyb();
+extern int stralloc_catb();
+extern int stralloc_append(); /* beware: this takes a pointer to 1 char */
+extern int stralloc_starts();
+
+#define stralloc_0(sa) stralloc_append(sa,"")
+
+#endif
diff --git a/stralloc_arts.c b/stralloc_arts.c
new file mode 100644 (file)
index 0000000..1ccb5a4
--- /dev/null
@@ -0,0 +1,12 @@
+#include "byte.h"
+#include "str.h"
+#include "stralloc.h"
+
+int stralloc_starts(sa,s)
+stralloc *sa;
+char *s;
+{
+  int len;
+  len = str_len(s);
+  return (sa->len >= len) && byte_equal(s,len,sa->s);
+}
diff --git a/stralloc_cat.c b/stralloc_cat.c
new file mode 100644 (file)
index 0000000..efbb112
--- /dev/null
@@ -0,0 +1,9 @@
+#include "byte.h"
+#include "stralloc.h"
+
+int stralloc_cat(sato,safrom)
+stralloc *sato;
+stralloc *safrom;
+{
+  return stralloc_catb(sato,safrom->s,safrom->len);
+}
diff --git a/stralloc_catb.c b/stralloc_catb.c
new file mode 100644 (file)
index 0000000..67dbcc0
--- /dev/null
@@ -0,0 +1,15 @@
+#include "stralloc.h"
+#include "byte.h"
+
+int stralloc_catb(sa,s,n)
+stralloc *sa;
+char *s;
+unsigned int n;
+{
+  if (!sa->s) return stralloc_copyb(sa,s,n);
+  if (!stralloc_readyplus(sa,n + 1)) return 0;
+  byte_copy(sa->s + sa->len,n,s);
+  sa->len += n;
+  sa->s[sa->len] = 'Z'; /* ``offensive programming'' */
+  return 1;
+}
diff --git a/stralloc_cats.c b/stralloc_cats.c
new file mode 100644 (file)
index 0000000..d300286
--- /dev/null
@@ -0,0 +1,10 @@
+#include "byte.h"
+#include "str.h"
+#include "stralloc.h"
+
+int stralloc_cats(sa,s)
+stralloc *sa;
+char *s;
+{
+  return stralloc_catb(sa,s,str_len(s));
+}
diff --git a/stralloc_copy.c b/stralloc_copy.c
new file mode 100644 (file)
index 0000000..652aed6
--- /dev/null
@@ -0,0 +1,9 @@
+#include "byte.h"
+#include "stralloc.h"
+
+int stralloc_copy(sato,safrom)
+stralloc *sato;
+stralloc *safrom;
+{
+  return stralloc_copyb(sato,safrom->s,safrom->len);
+}
diff --git a/stralloc_eady.c b/stralloc_eady.c
new file mode 100644 (file)
index 0000000..3a31f4b
--- /dev/null
@@ -0,0 +1,6 @@
+#include "alloc.h"
+#include "stralloc.h"
+#include "gen_allocdefs.h"
+
+GEN_ALLOC_ready(stralloc,char,s,len,a,i,n,x,30,stralloc_ready)
+GEN_ALLOC_readyplus(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus)
diff --git a/stralloc_opyb.c b/stralloc_opyb.c
new file mode 100644 (file)
index 0000000..ac258b3
--- /dev/null
@@ -0,0 +1,14 @@
+#include "stralloc.h"
+#include "byte.h"
+
+int stralloc_copyb(sa,s,n)
+stralloc *sa;
+char *s;
+unsigned int n;
+{
+  if (!stralloc_ready(sa,n + 1)) return 0;
+  byte_copy(sa->s,n,s);
+  sa->len = n;
+  sa->s[n] = 'Z'; /* ``offensive programming'' */
+  return 1;
+}
diff --git a/stralloc_opys.c b/stralloc_opys.c
new file mode 100644 (file)
index 0000000..fdd7807
--- /dev/null
@@ -0,0 +1,10 @@
+#include "byte.h"
+#include "str.h"
+#include "stralloc.h"
+
+int stralloc_copys(sa,s)
+stralloc *sa;
+char *s;
+{
+  return stralloc_copyb(sa,s,str_len(s));
+}
diff --git a/stralloc_pend.c b/stralloc_pend.c
new file mode 100644 (file)
index 0000000..a3443b8
--- /dev/null
@@ -0,0 +1,5 @@
+#include "alloc.h"
+#include "stralloc.h"
+#include "gen_allocdefs.h"
+
+GEN_ALLOC_append(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus,stralloc_append)
diff --git a/strerr.h b/strerr.h
new file mode 100644 (file)
index 0000000..d18e833
--- /dev/null
+++ b/strerr.h
@@ -0,0 +1,80 @@
+#ifndef STRERR_H
+#define STRERR_H
+
+struct strerr
+ {
+  struct strerr *who;
+  char *x;
+  char *y;
+  char *z;
+ }
+;
+
+extern struct strerr strerr_sys;
+extern void strerr_sysinit();
+
+extern char *strerr();
+extern void strerr_warn();
+extern void strerr_die();
+
+#define STRERR(r,se,a) \
+{ se.who = 0; se.x = a; se.y = 0; se.z = 0; return r; }
+
+#define STRERR_SYS(r,se,a) \
+{ se.who = &strerr_sys; se.x = a; se.y = 0; se.z = 0; return r; }
+#define STRERR_SYS3(r,se,a,b,c) \
+{ se.who = &strerr_sys; se.x = a; se.y = b; se.z = c; return r; }
+
+#define strerr_warn6(x1,x2,x3,x4,x5,x6,se) \
+strerr_warn((x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) (se))
+#define strerr_warn5(x1,x2,x3,x4,x5,se) \
+strerr_warn((x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) (se))
+#define strerr_warn4(x1,x2,x3,x4,se) \
+strerr_warn((x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_warn3(x1,x2,x3,se) \
+strerr_warn((x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_warn2(x1,x2,se) \
+strerr_warn((x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_warn1(x1,se) \
+strerr_warn((x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+
+#define strerr_die6(e,x1,x2,x3,x4,x5,x6,se) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) (se))
+#define strerr_die5(e,x1,x2,x3,x4,x5,se) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) (se))
+#define strerr_die4(e,x1,x2,x3,x4,se) \
+strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_die3(e,x1,x2,x3,se) \
+strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_die2(e,x1,x2,se) \
+strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+#define strerr_die1(e,x1,se) \
+strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) (se))
+
+#define strerr_die6sys(e,x1,x2,x3,x4,x5,x6) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),&strerr_sys)
+#define strerr_die5sys(e,x1,x2,x3,x4,x5) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,&strerr_sys)
+#define strerr_die4sys(e,x1,x2,x3,x4) \
+strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,&strerr_sys)
+#define strerr_die3sys(e,x1,x2,x3) \
+strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,&strerr_sys)
+#define strerr_die2sys(e,x1,x2) \
+strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,&strerr_sys)
+#define strerr_die1sys(e,x1) \
+strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,&strerr_sys)
+
+#define strerr_die6x(e,x1,x2,x3,x4,x5,x6) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(x6),(struct strerr *) 0)
+#define strerr_die5x(e,x1,x2,x3,x4,x5) \
+strerr_die((e),(x1),(x2),(x3),(x4),(x5),(char *) 0,(struct strerr *) 0)
+#define strerr_die4x(e,x1,x2,x3,x4) \
+strerr_die((e),(x1),(x2),(x3),(x4),(char *) 0,(char *) 0,(struct strerr *) 0)
+#define strerr_die3x(e,x1,x2,x3) \
+strerr_die((e),(x1),(x2),(x3),(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0)
+#define strerr_die2x(e,x1,x2) \
+strerr_die((e),(x1),(x2),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0)
+#define strerr_die1x(e,x1) \
+strerr_die((e),(x1),(char *) 0,(char *) 0,(char *) 0,(char *) 0,(char *) 0,(struct strerr *) 0)
+
+#endif
diff --git a/strerr_die.c b/strerr_die.c
new file mode 100644 (file)
index 0000000..6092020
--- /dev/null
@@ -0,0 +1,37 @@
+#include "substdio.h"
+#include "subfd.h"
+#include "exit.h"
+#include "strerr.h"
+
+void strerr_warn(x1,x2,x3,x4,x5,x6,se)
+char *x1; char *x2; char *x3; char *x4; char *x5; char *x6;
+struct strerr *se;
+{
+  strerr_sysinit();
+  if (x1) substdio_puts(subfderr,x1);
+  if (x2) substdio_puts(subfderr,x2);
+  if (x3) substdio_puts(subfderr,x3);
+  if (x4) substdio_puts(subfderr,x4);
+  if (x5) substdio_puts(subfderr,x5);
+  if (x6) substdio_puts(subfderr,x6);
+  while(se) {
+    if (se->x) substdio_puts(subfderr,se->x);
+    if (se->y) substdio_puts(subfderr,se->y);
+    if (se->z) substdio_puts(subfderr,se->z);
+    se = se->who;
+  }
+  substdio_puts(subfderr,"\n");
+  substdio_flush(subfderr);
+}
+
+void strerr_die(e,x1,x2,x3,x4,x5,x6,se)
+int e;
+char *x1; char *x2; char *x3; char *x4; char *x5; char *x6;
+struct strerr *se;
+{
+  strerr_warn(x1,x2,x3,x4,x5,x6,se);
+  _exit(e);
+}
diff --git a/strerr_sys.c b/strerr_sys.c
new file mode 100644 (file)
index 0000000..198198b
--- /dev/null
@@ -0,0 +1,12 @@
+#include "error.h"
+#include "strerr.h"
+
+struct strerr strerr_sys;
+
+void strerr_sysinit()
+{
+  strerr_sys.who = 0;
+  strerr_sys.x = error_str(errno);
+  strerr_sys.y = "";
+  strerr_sys.z = "";
+}
diff --git a/subfd.h b/subfd.h
new file mode 100644 (file)
index 0000000..bcb2e1e
--- /dev/null
+++ b/subfd.h
@@ -0,0 +1,15 @@
+#ifndef SUBFD_H
+#define SUBFD_H
+
+#include "substdio.h"
+
+extern substdio *subfdin;
+extern substdio *subfdinsmall;
+extern substdio *subfdout;
+extern substdio *subfdoutsmall;
+extern substdio *subfderr;
+
+extern int subfd_read();
+extern int subfd_readsmall();
+
+#endif
diff --git a/subfderr.c b/subfderr.c
new file mode 100644 (file)
index 0000000..011ab0f
--- /dev/null
@@ -0,0 +1,7 @@
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+
+char subfd_errbuf[256];
+static substdio it = SUBSTDIO_FDBUF(write,2,subfd_errbuf,256);
+substdio *subfderr = &it;
diff --git a/subfdin.c b/subfdin.c
new file mode 100644 (file)
index 0000000..a11d323
--- /dev/null
+++ b/subfdin.c
@@ -0,0 +1,13 @@
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+
+int subfd_read(fd,buf,len) int fd; char *buf; int len;
+{
+  if (substdio_flush(subfdout) == -1) return -1;
+  return read(fd,buf,len);
+}
+
+char subfd_inbuf[SUBSTDIO_INSIZE];
+static substdio it = SUBSTDIO_FDBUF(subfd_read,0,subfd_inbuf,SUBSTDIO_INSIZE);
+substdio *subfdin = &it;
diff --git a/subfdins.c b/subfdins.c
new file mode 100644 (file)
index 0000000..36983ac
--- /dev/null
@@ -0,0 +1,13 @@
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+
+int subfd_readsmall(fd,buf,len) int fd; char *buf; int len;
+{
+  if (substdio_flush(subfdoutsmall) == -1) return -1;
+  return read(fd,buf,len);
+}
+
+char subfd_inbufsmall[256];
+static substdio it = SUBSTDIO_FDBUF(subfd_readsmall,0,subfd_inbufsmall,256);
+substdio *subfdinsmall = &it;
diff --git a/subfdout.c b/subfdout.c
new file mode 100644 (file)
index 0000000..0aee102
--- /dev/null
@@ -0,0 +1,7 @@
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+
+char subfd_outbuf[SUBSTDIO_OUTSIZE];
+static substdio it = SUBSTDIO_FDBUF(write,1,subfd_outbuf,SUBSTDIO_OUTSIZE);
+substdio *subfdout = &it;
diff --git a/subfdouts.c b/subfdouts.c
new file mode 100644 (file)
index 0000000..5be356d
--- /dev/null
@@ -0,0 +1,7 @@
+#include "readwrite.h"
+#include "substdio.h"
+#include "subfd.h"
+
+char subfd_outbufsmall[256];
+static substdio it = SUBSTDIO_FDBUF(write,1,subfd_outbufsmall,256);
+substdio *subfdoutsmall = &it;
diff --git a/subgetopt.3 b/subgetopt.3
new file mode 100644 (file)
index 0000000..aae03aa
--- /dev/null
@@ -0,0 +1,357 @@
+.TH subgetopt 3
+.SH NAME
+subgetopt \- get option character from command line
+.SH SYNTAX
+.B #include <subgetopt.h>
+
+char *\fBsgoptarg\fP;
+.br
+int \fBsgoptind\fP;
+.br
+int \fBsgoptpos\fP;
+.br
+int \fBsgoptdone\fP;
+.br
+int \fBsgoptproblem\fP;
+
+int \fBsgopt(\fP\fIargc,argv,opts\fR\fB)\fP;
+
+int \fIargc\fR;
+.br
+char **\fIargv\fR;
+.br
+char *\fIopts\fR;
+.SH DESCRIPTION
+.B sgopt
+returns the next valid command-line option character
+from
+.IR argv .
+
+Valid option characters are listed in the
+.I opts
+string.
+.I opts
+may be empty.
+A character in
+.I opts
+may be followed by a colon,
+in which case it
+takes an
+.I option argument\fR.
+Avoid using the characters ?, :, and \- as option characters.
+
+Below
+.I option argument
+is abbreviated
+as
+.I optarg
+and
+.I command-line argument
+is abbreviated as
+.IR cmdarg .
+
+Options are listed in cmdargs which begin with
+a minus sign.
+Several options which do not take optargs may be combined
+into one cmdarg.
+
+An option which takes an optarg may be handled in two ways.
+If it appears at the very end of a cmdarg,
+then the entire next cmdarg is the optarg.
+But if there are any characters in the cmdarg
+after the option character,
+then those characters form the optarg.
+The optarg is returned in
+.BR sgoptarg .
+Next time
+.B sgopt
+looks at the cmdarg which follows the optarg.
+
+If a cmdarg does not begin with a hyphen,
+or if it is a lone hyphen not followed by any characters,
+or if it begins with two hyphens,
+then it terminates option processing,
+and
+.B sgopt
+returns an appropriate code.
+If there are two hyphens,
+.B sgopt
+will advance attention to the next cmdarg,
+so it can be called again to read further options.
+.SH "PROPER USAGE"
+.B sgoptproblem
+should be used only when
+.B sgopt
+returns ?.
+.B sgoptind
+and
+.B sgoptpos
+are defined all the time.
+.B sgoptarg
+is defined all the time;
+it is null unless
+.B sgopt
+has just returned an option with optarg.
+
+.B sgopt
+is typically used as follows.
+
+.EX
+#include <subgetopt.h>
+
+main(argc,argv) int argc; char **argv; { int opt;
+
+while ((opt = sgopt(argc,argv,"a:s")) != sgoptdone)
+.br
+  switch(opt) {
+.br
+    case 'a':
+.br
+      printf("opt a with optarg %s\\n",sgoptarg); break;
+.br
+    case 's':
+.br
+      printf("opt s with no optarg\\n"); break;
+.br
+    case '?':
+.br
+      if (argv[sgoptind] && (sgoptind < argc))
+.br
+        printf("illegal opt %c\\n",sgoptproblem);
+.br
+      else
+.br
+        printf("missing arg, opt %c\\n",sgoptproblem);
+.br
+      exit(1);
+.br
+  }
+
+argv += sgoptind;
+.br
+while (*argv) printf("argument %s\\n",*argv++);
+.br
+exit(0);
+.br
+}
+.EE
+
+The end of the command line is
+marked by either
+.IR argc ,
+or a null pointer in
+.IR argv ,
+whichever comes first.
+Normally
+these two markers coincide,
+so it is redundant
+to test for
+both
+.I argv\fB[sgoptind]
+and
+.B sgoptind < \fIargc\fR.
+The above code shows both tests as an illustration.
+
+.B Multiple option sets:
+One useful technique is to call
+.B sgopt
+with a primary
+.I opts
+until it returns EOF,
+then call
+.B sgopt
+with a secondary
+.I opts
+until it returns EOF.
+The user can provide primary options, then a double hyphen,
+and then secondary options.
+No special handling is needed if some or all of the options are
+omitted.
+The same technique can be used for any number of option sets
+in series.
+
+.B Multiple command lines:
+Before parsing a new
+.BR argv ,
+make sure to
+set
+.B sgoptind
+and
+.B sgoptpos
+back to
+1 and 0.
+.SH "PARSING STAGES"
+.B sgopt
+keeps track of its position in
+.I argv
+with
+.B sgoptind
+and
+.BR sgoptpos ,
+which are initialized to 1 and 0.
+It looks at
+.I argv\fB[sgoptind][sgoptpos]
+and following characters.
+
+.B sgopt
+indicates
+that no more options are available by
+returning
+.BR sgoptdone ,
+which is initialized to
+.BR SUBGETOPTDONE ,
+which is defined as \-1.
+
+.B sgopt
+begins by setting
+.B optarg
+to null.
+
+.B Ending conditions:
+If
+.I argv
+is null, or
+.B sgoptind
+is larger than
+.IR argc ,
+or the current cmdarg
+.I argv\fB[sgoptind]
+is null,
+then
+.B sgopt
+returns
+.BR optdone .
+
+.B Stage one:
+If the current character
+is zero,
+.B sgopt
+moves to the beginning of the next cmdarg.
+It then checks the ending conditions again.
+
+.B Stage two:
+If
+the current position is the begining of the cmdarg,
+.B sgopt
+checks whether
+the current character
+is a minus sign.
+If not it returns
+.BR optdone .
+It then
+moves
+to the next character.
+If that character is zero,
+.B sgopt
+moves
+back to the beginning of the cmdarg,
+and returns
+.BR sgoptdone .
+If the character is a minus sign,
+.B sgopt
+moves to the beginning of the next cmdarg,
+and returns
+.BR sgoptdone .
+
+.B Stage three:
+.B sgopt
+records the current character,
+.IR c ,
+and moves to the next character.
+There are three possibilities:
+(1)
+.I c
+is an option character without optarg in
+.IR opts ,
+or
+(2)
+.I c
+is an option character with optarg in
+.IR opts ,
+or
+(3)
+.I c
+does not appear in
+.IR opts .
+
+(1)
+If
+.I c
+appears as an option character without optarg in
+.IR opts ,
+.B sgopt
+returns
+.IR c .
+
+(2)
+If
+.I c
+appears as an option character with optarg in
+.IR opts ,
+.B sgopt
+sets
+.B sgoptarg
+to the current position,
+and moves to the next cmdarg.
+If
+.B sgoptarg
+is nonempty,
+.B sgopt
+returns
+.IR c .
+
+Then
+.B sgopt
+sets
+.B sgoptarg
+to
+the current cmdarg.
+If
+the current cmdarg is null,
+or past
+.IR argc ,
+.B sgopt
+sets
+.B sgoptproblem
+to
+.I c
+and returns ?.
+Otherwise
+.B sgopt
+moves to the next
+argument
+and returns
+.IR c .
+
+(2)
+If
+.I c
+does not appear in
+.IR opts ,
+.B sgopt
+sets
+.B sgoptproblem
+to
+.I c
+and returns ?.
+.SH "SYNTAX NOTE"
+.B sgopt
+is actually a macro abbreviation for
+.BR subgetopt .
+The external
+.B sg
+variables are also macros
+for
+.BR subget .
+These macros are defined in
+.BR <subgetopt.h> ,
+unless
+.B SUBGETOPTNOSHORT
+is defined
+when
+.B <subgetopt.h>
+is included.
+.SH VERSION
+subgetopt version 0.9, 931129.
+.SH AUTHOR
+Placed into the public domain by Daniel J. Bernstein.
diff --git a/subgetopt.c b/subgetopt.c
new file mode 100644 (file)
index 0000000..dacf376
--- /dev/null
@@ -0,0 +1,79 @@
+/* subgetopt.c, subgetopt.h: (yet another) improved getopt clone, inner layer
+D. J. Bernstein, djb@pobox.com.
+No dependencies.
+No system requirements.
+19970228: Cleanups.
+931129: Adapted from getopt.c.
+No known patent problems.
+
+Documentation in subgetopt.3.
+*/
+
+#define SUBGETOPTNOSHORT
+#include "subgetopt.h"
+
+#define sgopt subgetopt
+#define optind subgetoptind
+#define optpos subgetoptpos
+#define optarg subgetoptarg
+#define optproblem subgetoptproblem
+#define optdone subgetoptdone
+
+int optind = 1;
+int optpos = 0;
+char *optarg = 0;
+int optproblem = 0;
+int optdone = SUBGETOPTDONE;
+
+int sgopt(argc,argv,opts)
+int argc;
+char **argv;
+char *opts;
+{
+  int c;
+  char *s;
+
+  optarg = 0;
+  if (!argv || (optind >= argc) || !argv[optind]) return optdone;
+  if (optpos && !argv[optind][optpos]) {
+    ++optind;
+    optpos = 0;
+    if ((optind >= argc) || !argv[optind]) return optdone;
+  }
+  if (!optpos) {
+    if (argv[optind][0] != '-') return optdone;
+    ++optpos;
+    c = argv[optind][1];
+    if ((c == '-') || (c == 0)) {
+      if (c) ++optind;
+      optpos = 0;
+      return optdone;
+    }
+    /* otherwise c is reassigned below */
+  }
+  c = argv[optind][optpos];
+  ++optpos;
+  s = opts;
+  while (*s) {
+    if (c == *s) {
+      if (s[1] == ':') {
+        optarg = argv[optind] + optpos;
+        ++optind;
+        optpos = 0;
+        if (!*optarg) {
+          optarg = argv[optind];
+          if ((optind >= argc) || !optarg) { /* argument past end */
+            optproblem = c;
+            return '?';
+          }
+         ++optind;
+        }
+      }
+      return c;
+    }
+    ++s;
+    if (*s == ':') ++s;
+  }
+  optproblem = c;
+  return '?';
+}
diff --git a/subgetopt.h b/subgetopt.h
new file mode 100644 (file)
index 0000000..d26c62a
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef SUBGETOPT_H
+#define SUBGETOPT_H
+
+#ifndef SUBGETOPTNOSHORT
+#define sgopt subgetopt
+#define sgoptarg subgetoptarg
+#define sgoptind subgetoptind
+#define sgoptpos subgetoptpos
+#define sgoptproblem subgetoptproblem
+#define sgoptprogname subgetoptprogname
+#define sgoptdone subgetoptdone
+#endif
+
+#define SUBGETOPTDONE -1
+
+extern int subgetopt();
+extern char *subgetoptarg;
+extern int subgetoptind;
+extern int subgetoptpos;
+extern int subgetoptproblem;
+extern char *subgetoptprogname;
+extern int subgetoptdone;
+
+#endif
diff --git a/substdi.c b/substdi.c
new file mode 100644 (file)
index 0000000..42407a1
--- /dev/null
+++ b/substdi.c
@@ -0,0 +1,91 @@
+#include "substdio.h"
+#include "byte.h"
+#include "error.h"
+
+static int oneread(op,fd,buf,len)
+register int (*op)();
+register int fd;
+register char *buf;
+register int len;
+{
+  register int r;
+
+  for (;;) {
+    r = op(fd,buf,len);
+    if (r == -1) if (errno == error_intr) continue;
+    return r;
+  }
+}
+
+static int getthis(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  register int r;
+  register int q;
+  r = s->p;
+  q = r - len;
+  if (q > 0) { r = len; s->p = q; } else s->p = 0;
+  byte_copy(buf,r,s->x + s->n);
+  s->n += r;
+  return r;
+}
+
+int substdio_feed(s)
+register substdio *s;
+{
+  register int r;
+  register int q;
+
+  if (s->p) return s->p;
+  q = s->n;
+  r = oneread(s->op,s->fd,s->x,q);
+  if (r <= 0) return r;
+  s->p = r;
+  q -= r;
+  s->n = q;
+  if (q > 0) /* damn, gotta shift */ byte_copyr(s->x + q,r,s->x);
+  return r;
+}
+
+int substdio_bget(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  register int r;
+  if (s->p > 0) return getthis(s,buf,len);
+  r = s->n; if (r <= len) return oneread(s->op,s->fd,buf,r);
+  r = substdio_feed(s); if (r <= 0) return r;
+  return getthis(s,buf,len);
+}
+
+int substdio_get(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  register int r;
+  if (s->p > 0) return getthis(s,buf,len);
+  if (s->n <= len) return oneread(s->op,s->fd,buf,len);
+  r = substdio_feed(s); if (r <= 0) return r;
+  return getthis(s,buf,len);
+}
+
+char *substdio_peek(s)
+register substdio *s;
+{
+  return s->x + s->n;
+}
+
+void substdio_seek(s,len)
+register substdio *s;
+register int len;
+{
+  s->n += len;
+  s->p -= len;
+}
diff --git a/substdio.c b/substdio.c
new file mode 100644 (file)
index 0000000..d03dff2
--- /dev/null
@@ -0,0 +1,15 @@
+#include "substdio.h"
+
+void substdio_fdbuf(s,op,fd,buf,len)
+register substdio *s;
+register int (*op)();
+register int fd;
+register char *buf;
+register int len;
+{
+  s->x = buf;
+  s->fd = fd;
+  s->op = op;
+  s->p = 0;
+  s->n = len;
+}
diff --git a/substdio.h b/substdio.h
new file mode 100644 (file)
index 0000000..7a0de6e
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef SUBSTDIO_H
+#define SUBSTDIO_H
+
+typedef struct substdio {
+  char *x;
+  int p;
+  int n;
+  int fd;
+  int (*op)();
+} substdio;
+
+#define SUBSTDIO_FDBUF(op,fd,buf,len) { (buf), 0, (len), (fd), (op) }
+
+extern void substdio_fdbuf();
+
+extern int substdio_flush();
+extern int substdio_put();
+extern int substdio_bput();
+extern int substdio_putflush();
+extern int substdio_puts();
+extern int substdio_bputs();
+extern int substdio_putsflush();
+
+extern int substdio_get();
+extern int substdio_bget();
+extern int substdio_feed();
+
+extern char *substdio_peek();
+extern void substdio_seek();
+
+#define substdio_fileno(s) ((s)->fd)
+
+#define SUBSTDIO_INSIZE 8192
+#define SUBSTDIO_OUTSIZE 8192
+
+#define substdio_PEEK(s) ( (s)->x + (s)->n )
+#define substdio_SEEK(s,len) ( ( (s)->p -= (len) ) , ( (s)->n += (len) ) )
+
+extern int substdio_copy();
+
+#endif
diff --git a/substdio_copy.c b/substdio_copy.c
new file mode 100644 (file)
index 0000000..71cf200
--- /dev/null
@@ -0,0 +1,18 @@
+#include "substdio.h"
+
+int substdio_copy(ssout,ssin)
+register substdio *ssout;
+register substdio *ssin;
+{
+  register int n;
+  register char *x;
+
+  for (;;) {
+    n = substdio_feed(ssin);
+    if (n < 0) return -2;
+    if (!n) return 0;
+    x = substdio_PEEK(ssin);
+    if (substdio_put(ssout,x,n) == -1) return -3;
+    substdio_SEEK(ssin,n);
+  }
+}
diff --git a/substdo.c b/substdo.c
new file mode 100644 (file)
index 0000000..fb616f7
--- /dev/null
+++ b/substdo.c
@@ -0,0 +1,108 @@
+#include "substdio.h"
+#include "str.h"
+#include "byte.h"
+#include "error.h"
+
+static int allwrite(op,fd,buf,len)
+register int (*op)();
+register int fd;
+register char *buf;
+register int len;
+{
+  register int w;
+
+  while (len) {
+    w = op(fd,buf,len);
+    if (w == -1) {
+      if (errno == error_intr) continue;
+      return -1; /* note that some data may have been written */
+    }
+    if (w == 0) ; /* luser's fault */
+    buf += w;
+    len -= w;
+  }
+  return 0;
+}
+
+int substdio_flush(s)
+register substdio *s;
+{
+  register int p;
+  p = s->p;
+  if (!p) return 0;
+  s->p = 0;
+  return allwrite(s->op,s->fd,s->x,p);
+}
+
+int substdio_bput(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  register int n;
+  while (len > (n = s->n - s->p)) {
+    byte_copy(s->x + s->p,n,buf); s->p += n; buf += n; len -= n;
+    if (substdio_flush(s) == -1) return -1;
+  }
+  /* now len <= s->n - s->p */
+  byte_copy(s->x + s->p,len,buf);
+  s->p += len;
+  return 0;
+}
+
+int substdio_put(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  register int n;
+  n = s->n;
+  if (len > n - s->p) {
+    if (substdio_flush(s) == -1) return -1;
+    /* now s->p == 0 */
+    if (n < SUBSTDIO_OUTSIZE) n = SUBSTDIO_OUTSIZE;
+    while (len > s->n) {
+      if (n > len) n = len;
+      if (allwrite(s->op,s->fd,buf,n) == -1) return -1;
+      buf += n;
+      len -= n;
+    }
+  }
+  /* now len <= s->n - s->p */
+  byte_copy(s->x + s->p,len,buf);
+  s->p += len;
+  return 0;
+}
+
+int substdio_putflush(s,buf,len)
+register substdio *s;
+register char *buf;
+register int len;
+{
+  if (substdio_flush(s) == -1) return -1;
+  return allwrite(s->op,s->fd,buf,len);
+}
+
+int substdio_bputs(s,buf)
+register substdio *s;
+register char *buf;
+{
+  return substdio_bput(s,buf,str_len(buf));
+}
+
+int substdio_puts(s,buf)
+register substdio *s;
+register char *buf;
+{
+  return substdio_put(s,buf,str_len(buf));
+}
+
+int substdio_putsflush(s,buf)
+register substdio *s;
+register char *buf;
+{
+  return substdio_putflush(s,buf,str_len(buf));
+}
diff --git a/tcp-env.1 b/tcp-env.1
new file mode 100644 (file)
index 0000000..edd46f2
--- /dev/null
+++ b/tcp-env.1
@@ -0,0 +1,67 @@
+.TH tcp-env 1
+.SH NAME
+tcp-env \- set up TCP-related environment variables
+.SH SYNOPSIS
+.B tcp-env
+[
+.B \-rR
+]
+[
+.B \-t\fItimeout
+]
+.I program
+[
+.I arg ...
+]
+.SH DESCRIPTION
+The input for
+.B tcp-env
+must be a TCP connection.
+.B tcp-env
+finds out information about that connection,
+puts the information into several environment variables
+as described in
+.B tcp-environ(5),
+and runs
+.I program
+with the given arguments.
+
+Usually
+.B tcp-env
+is run from
+.BR inetd .
+It might instead be run from another server
+that already sets up the right environment variables;
+if
+.B PROTO
+is set to
+.B TCP
+when
+.B tcp-env
+is invoked,
+.B tcp-env
+assumes that all the other variables are set up properly,
+and it does not check whether the input is a TCP connection.
+.SH OPTIONS
+.TP
+.B \-r
+(Default.)
+Attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-R
+Do not attempt to obtain
+.B TCPREMOTEINFO
+from the remote host.
+.TP
+.B \-t\fItimeout
+Give up on the
+.B TCPREMOTEINFO
+connection attempt after
+.I timeout
+seconds.
+Default: 30.
+.SH "SEE ALSO"
+tcp-environ(5),
+inetd(8)
diff --git a/tcp-env.c b/tcp-env.c
new file mode 100644 (file)
index 0000000..feb85cc
--- /dev/null
+++ b/tcp-env.c
@@ -0,0 +1,129 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/param.h>
+#include <netinet/in.h>
+#include "sig.h"
+#include "stralloc.h"
+#include "str.h"
+#include "env.h"
+#include "fmt.h"
+#include "scan.h"
+#include "subgetopt.h"
+#include "ip.h"
+#include "dns.h"
+#include "byte.h"
+#include "remoteinfo.h"
+#include "exit.h"
+#include "case.h"
+
+void die() { _exit(111); }
+
+struct sockaddr_in salocal;
+unsigned long localport;
+struct ip_address iplocal;
+stralloc localname = {0};
+
+struct sockaddr_in saremote;
+unsigned long remoteport;
+struct ip_address ipremote;
+stralloc remotename = {0};
+
+char temp[IPFMT + FMT_ULONG];
+
+void main(argc,argv)
+int argc;
+char *argv[];
+{
+ int dummy;
+ char *proto;
+ int opt;
+ int flagremoteinfo;
+ unsigned long timeout;
+
+ sig_pipeignore();
+
+ flagremoteinfo = 1;
+ timeout = 30;
+ while ((opt = sgopt(argc,argv,"rRt:")) != sgoptdone)
+   switch(opt)
+    {
+     case 'r': flagremoteinfo = 1; break;
+     case 'R': flagremoteinfo = 0; break;
+     case 't': scan_ulong(sgoptarg,&timeout); break;
+    }
+
+ argv += sgoptind;
+ argc -= sgoptind;
+
+ if (argc < 1) die();
+ if (!env_init()) die();
+
+ proto = env_get("PROTO");
+ if (!proto || str_diff(proto,"TCP"))
+  {
+   if (!env_put("PROTO=TCP")) die();
+
+   dummy = sizeof(salocal);
+   if (getsockname(0,(struct sockaddr *) &salocal,&dummy) == -1) die();
+
+   localport = ntohs(salocal.sin_port);
+   temp[fmt_ulong(temp,localport)] = 0;
+   if (!env_put2("TCPLOCALPORT",temp)) die();
+
+   byte_copy(&iplocal,4,&salocal.sin_addr);
+   temp[ip_fmt(temp,&iplocal)] = 0;
+   if (!env_put2("TCPLOCALIP",temp)) die();
+
+   switch(dns_ptr(&localname,&iplocal))
+    {
+     case DNS_MEM: die();
+     case DNS_SOFT:
+       if (!stralloc_copys(&localname,"softdnserror")) die();
+     case 0:
+       if (!stralloc_0(&localname)) die();
+       case_lowers(localname.s);
+       if (!env_put2("TCPLOCALHOST",localname.s)) die();
+       break;
+     default:
+       if (!env_unset("TCPLOCALHOST")) die();
+    }
+
+   dummy = sizeof(saremote);
+   if (getpeername(0,(struct sockaddr *) &saremote,&dummy) == -1) die();
+
+   remoteport = ntohs(saremote.sin_port);
+   temp[fmt_ulong(temp,remoteport)] = 0;
+   if (!env_put2("TCPREMOTEPORT",temp)) die();
+
+   byte_copy(&ipremote,4,&saremote.sin_addr);
+   temp[ip_fmt(temp,&ipremote)] = 0;
+   if (!env_put2("TCPREMOTEIP",temp)) die();
+
+   switch(dns_ptr(&remotename,&ipremote))
+    {
+     case DNS_MEM: die();
+     case DNS_SOFT:
+       if (!stralloc_copys(&remotename,"softdnserror")) die();
+     case 0:
+       if (!stralloc_0(&remotename)) die();
+       case_lowers(remotename.s);
+       if (!env_put2("TCPREMOTEHOST",remotename.s)) die();
+       break;
+     default:
+       if (!env_unset("TCPREMOTEHOST")) die();
+    }
+
+   if (!env_unset("TCPREMOTEINFO")) die();
+   if (flagremoteinfo)
+    {
+     char *rinfo;
+     rinfo = remoteinfo_get(&ipremote,remoteport,&iplocal,localport,(int) timeout);
+     if (rinfo)
+       if (!env_put2("TCPREMOTEINFO",rinfo)) die();
+    }
+  }
+
+ sig_pipedefault();
+ execvp(*argv,argv);
+ die();
+}
diff --git a/tcp-environ.5 b/tcp-environ.5
new file mode 100644 (file)
index 0000000..b5cb83b
--- /dev/null
@@ -0,0 +1,62 @@
+.TH tcp-environ 5
+.SH NAME
+tcp-environ \- TCP-related environment variables
+.SH DESCRIPTION
+The following environment variables
+describe a TCP connection.
+They are set up by
+.BR tcp-env ,
+.BR tcpclient ,
+and
+.BR tcpserver .
+Note that
+.BR TCPLOCALHOST ,
+.BR TCPREMOTEHOST ,
+and
+.B TCPREMOTEINFO
+can contain arbitrary characters.
+.TP 5
+PROTO
+The string
+.BR TCP .
+.TP 5
+TCPLOCALHOST
+The domain name of the local host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the local IP address,
+.B TCPLOCALHOST
+is not set.
+.TP 5
+TCPLOCALIP
+The IP address of the local host, in dotted-decimal form.
+.TP 5
+TCPLOCALPORT
+The local TCP port number, in decimal.
+.TP 5
+TCPREMOTEHOST
+The domain name of the remote host,
+with uppercase letters converted to lowercase.
+If there is no currently available domain name
+for the remote IP address,
+.B TCPREMOTEHOST
+is not set.
+.TP 5
+TCPREMOTEINFO
+A connection-specific string, perhaps a username,
+supplied by the remote host
+via 931/1413/IDENT/TAP.
+If the remote host did not supply connection information,
+.B TCPREMOTEINFO
+is not set.
+.TP 5
+TCPREMOTEIP
+The IP address of the remote host.
+.TP 5
+TCPREMOTEPORT
+The remote TCP port number.
+.SH "SEE ALSO"
+tcpclient(1),
+tcpserver(1),
+tcp-env(1),
+tcp(4)
diff --git a/tcpto.c b/tcpto.c
new file mode 100644 (file)
index 0000000..8d100d5
--- /dev/null
+++ b/tcpto.c
@@ -0,0 +1,165 @@
+#include "tcpto.h"
+#include "open.h"
+#include "lock.h"
+#include "seek.h"
+#include "now.h"
+#include "ip.h"
+#include "byte.h"
+#include "datetime.h"
+#include "readwrite.h"
+
+char tcpto_buf[1024];
+
+static int flagwasthere;
+static int fdlock;
+
+static int getbuf()
+{
+ int r;
+ int fd;
+
+ fdlock = open_write("queue/lock/tcpto");
+ if (fdlock == -1) return 0;
+ fd = open_read("queue/lock/tcpto");
+ if (fd == -1) { close(fdlock); return 0; }
+ if (lock_ex(fdlock) == -1) { close(fdlock); close(fd); return 0; }
+ r = read(fd,tcpto_buf,sizeof(tcpto_buf));
+ close(fd);
+ if (r < 0) { close(fdlock); return 0; }
+ r >>= 4;
+ if (!r) close(fdlock);
+ return r;
+}
+
+int tcpto(ip) struct ip_address *ip;
+{
+ int n;
+ int i;
+ char *record;
+ datetime_sec when;
+
+ flagwasthere = 0;
+
+ n = getbuf();
+ if (!n) return 0;
+ close(fdlock);
+
+ record = tcpto_buf;
+ for (i = 0;i < n;++i)
+  {
+   if (byte_equal(ip->d,4,record))
+    {
+     flagwasthere = 1;
+     if (record[4] >= 2)
+      {
+       when = (unsigned long) (unsigned char) record[11];
+       when = (when << 8) + (unsigned long) (unsigned char) record[10];
+       when = (when << 8) + (unsigned long) (unsigned char) record[9];
+       when = (when << 8) + (unsigned long) (unsigned char) record[8];
+
+       if (now() - when < ((60 + (getpid() & 31)) << 6))
+        return 1;
+      }
+     return 0;
+    }
+   record += 16;
+  }
+ return 0;
+}
+
+void tcpto_err(ip,flagerr) struct ip_address *ip; int flagerr;
+{
+ int n;
+ int i;
+ char *record;
+ datetime_sec when;
+ datetime_sec firstwhen;
+ int firstpos;
+ datetime_sec lastwhen;
+
+ if (!flagerr)
+   if (!flagwasthere)
+     return; /* could have been added, but not worth the effort to check */
+
+ n = getbuf();
+ if (!n) return;
+
+ record = tcpto_buf;
+ for (i = 0;i < n;++i)
+  {
+   if (byte_equal(ip->d,4,record))
+    {
+     if (!flagerr)
+       record[4] = 0;
+     else
+      {
+       lastwhen = (unsigned long) (unsigned char) record[11];
+       lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[10];
+       lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[9];
+       lastwhen = (lastwhen << 8) + (unsigned long) (unsigned char) record[8];
+       when = now();
+
+       if (record[4] && (when < 120 + lastwhen)) { close(fdlock); return; }
+
+       if (++record[4] > 10) record[4] = 10;
+       record[8] = when; when >>= 8;
+       record[9] = when; when >>= 8;
+       record[10] = when; when >>= 8;
+       record[11] = when;
+      }
+     if (seek_set(fdlock,i << 4) == 0)
+       if (write(fdlock,record,16) < 16)
+         ; /*XXX*/
+     close(fdlock);
+     return;
+    }
+   record += 16;
+  }
+
+ if (!flagerr) { close(fdlock); return; }
+
+ record = tcpto_buf;
+ for (i = 0;i < n;++i)
+  {
+   if (!record[4]) break;
+   record += 16;
+  }
+
+ if (i >= n)
+  {
+   firstpos = -1;
+   record = tcpto_buf;
+   for (i = 0;i < n;++i)
+    {
+     when = (unsigned long) (unsigned char) record[11];
+     when = (when << 8) + (unsigned long) (unsigned char) record[10];
+     when = (when << 8) + (unsigned long) (unsigned char) record[9];
+     when = (when << 8) + (unsigned long) (unsigned char) record[8];
+     when += (record[4] << 10);
+     if ((firstpos < 0) || (when < firstwhen))
+      {
+       firstpos = i;
+       firstwhen = when;
+      }
+     record += 16;
+    }
+   i = firstpos;
+  }
+
+ if (i >= 0)
+  {
+   record = tcpto_buf + (i << 4);
+   byte_copy(record,4,ip->d);
+   when = now();
+   record[8] = when; when >>= 8;
+   record[9] = when; when >>= 8;
+   record[10] = when; when >>= 8;
+   record[11] = when;
+   record[4] = 1;
+   if (seek_set(fdlock,i << 4) == 0)
+     if (write(fdlock,record,16) < 16)
+       ; /*XXX*/
+  }
+
+ close(fdlock);
+}
diff --git a/tcpto.h b/tcpto.h
new file mode 100644 (file)
index 0000000..cd44a42
--- /dev/null
+++ b/tcpto.h
@@ -0,0 +1,8 @@
+#ifndef TCPTO_H
+#define TCPTO_H
+
+extern int tcpto();
+extern void tcpto_err();
+extern void tcpto_clean();
+
+#endif
diff --git a/tcpto_clean.c b/tcpto_clean.c
new file mode 100644 (file)
index 0000000..ed48506
--- /dev/null
@@ -0,0 +1,20 @@
+#include "tcpto.h"
+#include "open.h"
+#include "substdio.h"
+#include "readwrite.h"
+
+char tcpto_cleanbuf[1024];
+
+void tcpto_clean() /* running from queue/mess */
+{
+ int fd;
+ int i;
+ substdio ss;
+
+ fd = open_write("../lock/tcpto");
+ if (fd == -1) return;
+ substdio_fdbuf(&ss,write,fd,tcpto_cleanbuf,sizeof(tcpto_cleanbuf));
+ for (i = 0;i < sizeof(tcpto_cleanbuf);++i) substdio_put(&ss,"",1);
+ substdio_flush(&ss); /* if it fails, bummer */
+ close(fd);
+}
diff --git a/timeoutconn.c b/timeoutconn.c
new file mode 100644 (file)
index 0000000..33a16d9
--- /dev/null
@@ -0,0 +1,59 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include "ndelay.h"
+#include "select.h"
+#include "error.h"
+#include "readwrite.h"
+#include "ip.h"
+#include "byte.h"
+#include "timeoutconn.h"
+
+int timeoutconn(s,ip,port,timeout)
+int s;
+struct ip_address *ip;
+unsigned int port;
+int timeout;
+{
+  char ch;
+  struct sockaddr_in sin;
+  char *x;
+  fd_set wfds;
+  struct timeval tv;
+  byte_zero(&sin,sizeof(sin));
+  byte_copy(&sin.sin_addr,4,ip);
+  x = (char *) &sin.sin_port;
+  x[1] = port; port >>= 8; x[0] = port;
+  sin.sin_family = AF_INET;
+  if (ndelay_on(s) == -1) return -1;
+  /* XXX: could bind s */
+  if (connect(s,(struct sockaddr *) &sin,sizeof(sin)) == 0) {
+    ndelay_off(s);
+    return 0;
+  }
+  if ((errno != error_inprogress) && (errno != error_wouldblock)) return -1;
+  FD_ZERO(&wfds);
+  FD_SET(s,&wfds);
+  tv.tv_sec = timeout; tv.tv_usec = 0;
+  if (select(s + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv) == -1) return -1;
+  if (FD_ISSET(s,&wfds)) {
+    int dummy;
+    dummy = sizeof(sin);
+    if (getpeername(s,(struct sockaddr *) &sin,&dummy) == -1) {
+      read(s,&ch,1);
+      return -1;
+    }
+    ndelay_off(s);
+    return 0;
+  }
+  errno = error_timeout; /* note that connect attempt is continuing */
+  return -1;
+}
diff --git a/timeoutconn.h b/timeoutconn.h
new file mode 100644 (file)
index 0000000..88aab06
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef TIMEOUTCONN_H
+#define TIMEOUTCONN_H
+
+extern int timeoutconn();
+
+#endif
diff --git a/timeoutread.c b/timeoutread.c
new file mode 100644 (file)
index 0000000..4cebf54
--- /dev/null
@@ -0,0 +1,25 @@
+#include "timeoutread.h"
+#include "select.h"
+#include "error.h"
+#include "readwrite.h"
+
+int timeoutread(fdt,buf,len) int fdt; char *buf; int len;
+{
+  fd_set rfds;
+  struct timeval tv;
+  int fd;
+
+  tv.tv_sec = (fdt >> 10);
+  tv.tv_usec = 0;
+
+  fd = (fdt & 1023);
+  FD_ZERO(&rfds);
+  FD_SET(fd,&rfds);
+
+  if (select(fd + 1,&rfds,(fd_set *) 0,(fd_set *) 0,&tv) == -1) return -1;
+  if (FD_ISSET(fd,&rfds)) return read(fd,buf,len);
+
+  shutdown(fd,0);
+  errno = error_timeout;
+  return -1;
+}
diff --git a/timeoutread.h b/timeoutread.h
new file mode 100644 (file)
index 0000000..c1d19a0
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef TIMEOUTREAD_H
+#define TIMEOUTREAD_H
+
+#define TIMEOUTREAD(s,fd) (((s) << 10) | (fd))
+
+extern int timeoutread();
+
+#endif
diff --git a/timeoutwrite.c b/timeoutwrite.c
new file mode 100644 (file)
index 0000000..8a09c8a
--- /dev/null
@@ -0,0 +1,25 @@
+#include "timeoutwrite.h"
+#include "select.h"
+#include "error.h"
+#include "readwrite.h"
+
+int timeoutwrite(fdt,buf,len) int fdt; char *buf; int len;
+{
+  fd_set wfds;
+  struct timeval tv;
+  int fd;
+
+  tv.tv_sec = (fdt >> 10);
+  tv.tv_usec = 0;
+
+  fd = (fdt & 1023);
+  FD_ZERO(&wfds);
+  FD_SET(fd,&wfds);
+
+  if (select(fd + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv) == -1) return -1;
+  if (FD_ISSET(fd,&wfds)) return write(fd,buf,len);
+
+  shutdown(fd,1);
+  errno = error_timeout;
+  return -1;
+}
diff --git a/timeoutwrite.h b/timeoutwrite.h
new file mode 100644 (file)
index 0000000..c93aa18
--- /dev/null
@@ -0,0 +1,8 @@
+#ifndef TIMEOUTWRITE_H
+#define TIMEOUTWRITE_H
+
+#define TIMEOUTWRITE(s,fd) (((s) << 10) | (fd))
+
+extern int timeoutwrite();
+
+#endif
diff --git a/token822.c b/token822.c
new file mode 100644 (file)
index 0000000..7760159
--- /dev/null
@@ -0,0 +1,511 @@
+#include "stralloc.h"
+#include "alloc.h"
+#include "str.h"
+#include "token822.h"
+#include "gen_allocdefs.h"
+
+static struct token822 comma = { TOKEN822_COMMA };
+
+void token822_reverse(ta)
+token822_alloc *ta;
+{
+ int i;
+ int n;
+ struct token822 temp;
+
+ n = ta->len - 1;
+ for (i = 0;i + i < n;++i)
+  {
+   temp = ta->t[i];
+   ta->t[i] = ta->t[n - i];
+   ta->t[n - i] = temp;
+  }
+}
+
+GEN_ALLOC_ready(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_ready)
+GEN_ALLOC_readyplus(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus)
+GEN_ALLOC_append(token822_alloc,struct token822,t,len,a,i,n,x,30,token822_readyplus,token822_append)
+
+static int needspace(t1,t2)
+int t1;
+int t2;
+{
+ if (!t1) return 0;
+ if (t1 == TOKEN822_COLON) return 1;
+ if (t1 == TOKEN822_COMMA) return 1;
+ if (t2 == TOKEN822_LEFT) return 1;
+ switch(t1)
+  {
+   case TOKEN822_ATOM: case TOKEN822_LITERAL:
+   case TOKEN822_QUOTE: case TOKEN822_COMMENT:
+     switch(t2)
+      {
+       case TOKEN822_ATOM: case TOKEN822_LITERAL:
+       case TOKEN822_QUOTE: case TOKEN822_COMMENT:
+         return 1;
+      }
+  }
+ return 0;
+}
+
+static int atomok(ch)
+char ch;
+{
+ switch(ch)
+  {
+   case ' ': case '\t': case '\r': case '\n':
+   case '(': case '[': case '"':
+   case '<': case '>': case ';': case ':':
+   case '@': case ',': case '.':
+     return 0;
+  }
+ return 1;
+}
+
+static void atomcheck(t)
+struct token822 *t;
+{
+ int i;
+ char ch;
+ for (i = 0;i < t->slen;++i)
+  {
+   ch = t->s[i];
+   if ((ch < 32) || (ch > 126) || (ch == ')') || (ch == ']') || (ch == '\\'))
+    {
+     t->type = TOKEN822_QUOTE;
+     return;
+    }
+  }
+}
+
+int token822_unparse(sa,ta,linelen)
+stralloc *sa;
+token822_alloc *ta;
+unsigned int linelen;
+{
+ struct token822 *t;
+ int len;
+ int ch;
+ int i;
+ int j;
+ int lasttype;
+ int newtype;
+ char *s;
+ char *lineb;
+ char *linee;
+
+ len = 0;
+ lasttype = 0;
+ for (i = 0;i < ta->len;++i)
+  {
+   t = ta->t + i;
+   newtype = t->type;
+   if (needspace(lasttype,newtype))
+     ++len;
+   lasttype = newtype;
+   switch(newtype)
+    {
+     case TOKEN822_COMMA:
+       len += 3; break;
+     case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT: case TOKEN822_RIGHT:
+     case TOKEN822_SEMI: case TOKEN822_COLON:
+       ++len; break;
+     case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT:
+       if (t->type != TOKEN822_ATOM) len += 2;
+       for (j = 0;j < t->slen;++j)
+        switch(ch = t->s[j])
+         {
+          case '"': case '[': case ']': case '(': case ')':
+          case '\\': case '\r': case '\n': ++len;
+          default: ++len;
+         }
+       break;
+    }
+  }
+ len += 2;
+
+ if (!stralloc_ready(sa,len))
+   return -1;
+
+ s = sa->s;
+ lineb = s;
+ linee = 0;
+
+ lasttype = 0;
+ for (i = 0;i < ta->len;++i)
+  {
+   t = ta->t + i;
+   newtype = t->type;
+   if (needspace(lasttype,newtype))
+     *s++ = ' ';
+   lasttype = newtype;
+   switch(newtype)
+    {
+     case TOKEN822_COMMA:
+       *s++ = ',';
+#define NSUW \
+ s[0] = '\n'; s[1] = ' '; \
+ if (linee && (!linelen || (s - lineb <= linelen))) \
+  { while (linee < s) { linee[0] = linee[2]; ++linee; } linee -= 2; } \
+ else { if (linee) lineb = linee + 1; linee = s; s += 2; }
+       NSUW
+       break;
+     case TOKEN822_AT: *s++ = '@'; break;
+     case TOKEN822_DOT: *s++ = '.'; break;
+     case TOKEN822_LEFT: *s++ = '<'; break;
+     case TOKEN822_RIGHT: *s++ = '>'; break;
+     case TOKEN822_SEMI: *s++ = ';'; break;
+     case TOKEN822_COLON: *s++ = ':'; break;
+     case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL: case TOKEN822_COMMENT:
+       if (t->type == TOKEN822_QUOTE) *s++ = '"';
+       if (t->type == TOKEN822_LITERAL) *s++ = '[';
+       if (t->type == TOKEN822_COMMENT) *s++ = '(';
+       for (j = 0;j < t->slen;++j)
+        switch(ch = t->s[j])
+         {
+          case '"': case '[': case ']': case '(': case ')':
+          case '\\': case '\r': case '\n': *s++ = '\\';
+          default: *s++ = ch;
+         }
+       if (t->type == TOKEN822_QUOTE) *s++ = '"';
+       if (t->type == TOKEN822_LITERAL) *s++ = ']';
+       if (t->type == TOKEN822_COMMENT) *s++ = ')';
+       break;
+    }
+  }
+ NSUW
+ --s;
+ sa->len = s - sa->s;
+ return 1;
+}
+
+int token822_unquote(sa,ta)
+stralloc *sa;
+token822_alloc *ta;
+{
+ struct token822 *t;
+ int len;
+ int i;
+ int j;
+ char *s;
+
+ len = 0;
+ for (i = 0;i < ta->len;++i)
+  {
+   t = ta->t + i;
+   switch(t->type)
+    {
+     case TOKEN822_COMMA: case TOKEN822_AT: case TOKEN822_DOT: case TOKEN822_LEFT: 
+     case TOKEN822_RIGHT: case TOKEN822_SEMI: case TOKEN822_COLON: 
+       ++len; break;
+     case TOKEN822_LITERAL:
+       len += 2;
+     case TOKEN822_ATOM: case TOKEN822_QUOTE:
+       len += t->slen;
+    }
+  }
+
+ if (!stralloc_ready(sa,len))
+   return -1;
+
+ s = sa->s;
+
+ for (i = 0;i < ta->len;++i)
+  {
+   t = ta->t + i;
+   switch(t->type)
+    {
+     case TOKEN822_COMMA: *s++ = ','; break;
+     case TOKEN822_AT: *s++ = '@'; break;
+     case TOKEN822_DOT: *s++ = '.'; break;
+     case TOKEN822_LEFT: *s++ = '<'; break;
+     case TOKEN822_RIGHT: *s++ = '>'; break;
+     case TOKEN822_SEMI: *s++ = ';'; break;
+     case TOKEN822_COLON: *s++ = ':'; break;
+     case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+       if (t->type == TOKEN822_LITERAL) *s++ = '[';
+       for (j = 0;j < t->slen;++j)
+        *s++ = t->s[j];
+       if (t->type == TOKEN822_LITERAL) *s++ = ']';
+       break;
+     case TOKEN822_COMMENT: break;
+    }
+  }
+ sa->len = s - sa->s;
+ return 1;
+}
+
+int token822_parse(ta,sa,buf)
+token822_alloc *ta;
+stralloc *sa;
+stralloc *buf;
+{
+ int i;
+ int salen;
+ int level;
+ struct token822 *t;
+ int numtoks;
+ int numchars;
+ char *cbuf;
+
+ salen = sa->len;
+
+ numchars = 0;
+ numtoks = 0;
+ for (i = 0;i < salen;++i)
+   switch(sa->s[i])
+    {
+     case '.': case ',': case '@': case '<': case '>': case ':': case ';':
+       ++numtoks; break;
+     case ' ': case '\t': case '\r': case '\n': break;
+     case ')': case ']': return 0;
+     /* other control chars and non-ASCII chars are also bad, in theory */
+     case '(':
+       level = 1;
+       while (level)
+       {
+        if (++i >= salen) return 0;
+        switch(sa->s[i])
+         {
+          case '(': ++level; break;
+          case ')': --level; break;
+          case '\\': if (++i >= salen) return 0;
+          default: ++numchars;
+         }
+       }
+       ++numtoks;
+       break;
+     case '"':
+       level = 1;
+       while (level)
+       {
+        if (++i >= salen) return 0;
+        switch(sa->s[i])
+         {
+          case '"': --level; break;
+          case '\\': if (++i >= salen) return 0;
+          default: ++numchars;
+         }
+       }
+       ++numtoks;
+       break;
+     case '[':
+       level = 1;
+       while (level)
+       {
+        if (++i >= salen) return 0;
+        switch(sa->s[i])
+         {
+          case ']': --level; break;
+          case '\\': if (++i >= salen) return 0;
+          default: ++numchars;
+         }
+       }
+       ++numtoks;
+       break;
+     default:
+       do
+       {
+        ++numchars;
+        if (++i >= salen)
+          break;
+       }
+       while (atomok(sa->s[i]));
+       --i;
+       ++numtoks;
+    }
+
+ if (!token822_ready(ta,numtoks))
+   return -1;
+ if (!stralloc_ready(buf,numchars))
+   return -1;
+ cbuf = buf->s;
+ ta->len = numtoks;
+
+ t = ta->t;
+ for (i = 0;i < salen;++i)
+   switch(sa->s[i])
+    {
+     case '.': t->type = TOKEN822_DOT; ++t; break;
+     case ',': t->type = TOKEN822_COMMA; ++t; break;
+     case '@': t->type = TOKEN822_AT; ++t; break;
+     case '<': t->type = TOKEN822_LEFT; ++t; break;
+     case '>': t->type = TOKEN822_RIGHT; ++t; break;
+     case ':': t->type = TOKEN822_COLON; ++t; break;
+     case ';': t->type = TOKEN822_SEMI; ++t; break;
+     case ' ': case '\t': case '\r': case '\n': break;
+     case '(':
+       t->type = TOKEN822_COMMENT; t->s = cbuf; t->slen = 0;
+       level = 1;
+       while (level)
+       {
+        ++i; /* assert: < salen */
+        switch(sa->s[i])
+         {
+          case '(': ++level; break;
+          case ')': --level; break;
+          case '\\': ++i; /* assert: < salen */
+          default: *cbuf++ = sa->s[i]; ++t->slen;
+         }
+       }
+       ++t;
+       break;
+     case '"':
+       t->type = TOKEN822_QUOTE; t->s = cbuf; t->slen = 0;
+       level = 1;
+       while (level)
+       {
+        ++i; /* assert: < salen */
+        switch(sa->s[i])
+         {
+          case '"': --level; break;
+          case '\\': ++i; /* assert: < salen */
+          default: *cbuf++ = sa->s[i]; ++t->slen;
+         }
+       }
+       ++t;
+       break;
+     case '[':
+       t->type = TOKEN822_LITERAL; t->s = cbuf; t->slen = 0;
+       level = 1;
+       while (level)
+       {
+        ++i; /* assert: < salen */
+        switch(sa->s[i])
+         {
+          case ']': --level; break;
+          case '\\': ++i; /* assert: < salen */
+          default: *cbuf++ = sa->s[i]; ++t->slen;
+         }
+       }
+       ++t;
+       break;
+     default:
+       t->type = TOKEN822_ATOM; t->s = cbuf; t->slen = 0;
+       do
+       {
+        *cbuf++ = sa->s[i]; ++t->slen;
+        if (++i >= salen)
+          break;
+       }
+       while (atomok(sa->s[i]));
+       atomcheck(t);
+       --i;
+       ++t;
+    }
+ return 1;
+}
+
+static int gotaddr(taout,taaddr,callback)
+token822_alloc *taout;
+token822_alloc *taaddr;
+int (*callback)();
+{
+ int i;
+
+ if (callback(taaddr) != 1)
+   return 0;
+
+ if (!token822_readyplus(taout,taaddr->len))
+   return 0;
+ for (i = 0;i < taaddr->len;++i)
+   taout->t[taout->len++] = taaddr->t[i];
+
+ taaddr->len = 0;
+ return 1;
+}
+
+int token822_addrlist(taout,taaddr,ta,callback)
+token822_alloc *taout;
+token822_alloc *taaddr;
+token822_alloc *ta;
+int (*callback)();
+{
+ struct token822 *t;
+ struct token822 *beginning;
+ int ingroup;
+ int wordok;
+
+ taout->len = 0;
+ taaddr->len = 0;
+
+ if (!token822_readyplus(taout,1)) return -1;
+ if (!token822_readyplus(taaddr,1)) return -1;
+ ingroup = 0;
+ wordok = 1;
+
+ beginning = ta->t + 2;
+ t = ta->t + ta->len - 1;
+
+ /* rfc 822 address lists are easy to parse from right to left */
+
+#define FLUSH if (taaddr->len) if (!gotaddr(taout,taaddr,callback)) return -1;
+#define FLUSHCOMMA if (taaddr->len) { \
+if (!gotaddr(taout,taaddr,callback)) return -1; \
+if (!token822_append(taout,&comma)) return -1; }
+#define ADDRLEFT if (!token822_append(taaddr,t--)) return -1;
+#define OUTLEFT if (!token822_append(taout,t--)) return -1;
+
+ while (t >= beginning)
+  {
+   switch(t->type)
+    {
+     case TOKEN822_SEMI:
+       FLUSHCOMMA
+       if (ingroup) return 0;
+       ingroup = 1;
+       wordok = 1;
+       break;
+     case TOKEN822_COLON:
+       FLUSH
+       if (!ingroup) return 0;
+       ingroup = 0;
+       while ((t >= beginning) && (t->type != TOKEN822_COMMA))
+        OUTLEFT
+       if (t >= beginning)
+        OUTLEFT
+       wordok = 1;
+       continue;
+     case TOKEN822_RIGHT:
+       FLUSHCOMMA
+       OUTLEFT
+       while ((t >= beginning) && (t->type != TOKEN822_LEFT))
+        ADDRLEFT
+       /* important to use address here even if it's empty: <> */
+       if (!gotaddr(taout,taaddr,callback)) return -1;
+       if (t < beginning) return 0;
+       OUTLEFT
+       while ((t >= beginning) && ((t->type == TOKEN822_COMMENT) || (t->type == TOKEN822_ATOM) || (t->type == TOKEN822_QUOTE) || (t->type == TOKEN822_AT) || (t->type == TOKEN822_DOT)))
+        OUTLEFT
+       wordok = 0;
+       continue;
+     case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+       if (!wordok)
+        FLUSHCOMMA
+       wordok = 0;
+       ADDRLEFT
+       continue;
+     case TOKEN822_COMMENT:
+       /* comment is lexically a space; shouldn't affect wordok */
+       break;
+     case TOKEN822_COMMA:
+       FLUSH
+       wordok = 1;
+       break;
+     default:
+       wordok = 1;
+       ADDRLEFT
+       continue;
+    }
+   OUTLEFT
+  }
+ FLUSH
+ ++t;
+ while (t > ta->t)
+   if (!token822_append(taout,--t)) return -1;
+
+ token822_reverse(taout);
+ return 1;
+}
diff --git a/token822.h b/token822.h
new file mode 100644 (file)
index 0000000..9ca35cf
--- /dev/null
@@ -0,0 +1,37 @@
+#ifndef TOKEN822_H
+#define TOKEN822_H
+
+struct token822
+ {
+  int type;
+  char *s;
+  int slen;
+ }
+;
+
+#include "gen_alloc.h"
+GEN_ALLOC_typedef(token822_alloc,struct token822,t,len,a)
+
+extern int token822_parse();
+extern int token822_addrlist();
+extern int token822_unquote();
+extern int token822_unparse();
+extern void token822_free();
+extern void token822_reverse();
+extern int token822_ready();
+extern int token822_readyplus();
+extern int token822_append();
+
+#define TOKEN822_ATOM 1
+#define TOKEN822_QUOTE 2
+#define TOKEN822_LITERAL 3
+#define TOKEN822_COMMENT 4
+#define TOKEN822_LEFT 5
+#define TOKEN822_RIGHT 6
+#define TOKEN822_AT 7
+#define TOKEN822_COMMA 8
+#define TOKEN822_SEMI 9
+#define TOKEN822_COLON 10
+#define TOKEN822_DOT 11
+
+#endif
diff --git a/trigger.c b/trigger.c
new file mode 100644 (file)
index 0000000..39f81b8
--- /dev/null
+++ b/trigger.c
@@ -0,0 +1,41 @@
+#include "select.h"
+#include "open.h"
+#include "trigger.h"
+#include "hasnpbg1.h"
+
+static int fd = -1;
+#ifdef HASNAMEDPIPEBUG1
+static int fdw = -1;
+#endif
+
+void trigger_set()
+{
+ if (fd != -1)
+   close(fd);
+#ifdef HASNAMEDPIPEBUG1
+ if (fdw != -1)
+   close(fdw);
+#endif
+ fd = open_read("lock/trigger");
+#ifdef HASNAMEDPIPEBUG1
+ fdw = open_write("lock/trigger");
+#endif
+}
+
+void trigger_selprep(nfds,rfds)
+int *nfds;
+fd_set *rfds;
+{
+ if (fd != -1)
+  {
+   FD_SET(fd,rfds);
+   if (*nfds < fd + 1) *nfds = fd + 1;
+  }
+}
+
+int trigger_pulled(rfds)
+fd_set *rfds;
+{
+ if (fd != -1) if (FD_ISSET(fd,rfds)) return 1;
+ return 0;
+}
diff --git a/trigger.h b/trigger.h
new file mode 100644 (file)
index 0000000..dec24ef
--- /dev/null
+++ b/trigger.h
@@ -0,0 +1,8 @@
+#ifndef TRIGGER_H
+#define TRIGGER_H
+
+extern void trigger_set();
+extern void trigger_selprep();
+extern int trigger_pulled();
+
+#endif
diff --git a/triggerpull.c b/triggerpull.c
new file mode 100644 (file)
index 0000000..30b9a97
--- /dev/null
@@ -0,0 +1,16 @@
+#include "ndelay.h"
+#include "open.h"
+#include "triggerpull.h"
+
+void triggerpull()
+{
+ int fd;
+
+ fd = open_write("lock/trigger");
+ if (fd >= 0)
+  {
+   ndelay_on(fd);
+   write(fd,"",1); /* if it fails, bummer */
+   close(fd);
+  }
+}
diff --git a/triggerpull.h b/triggerpull.h
new file mode 100644 (file)
index 0000000..6d097bb
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef TRIGGERPULL_H
+#define TRIGGERPULL_H
+
+extern void triggerpull();
+
+#endif
diff --git a/trycpp.c b/trycpp.c
new file mode 100644 (file)
index 0000000..d7d83ad
--- /dev/null
+++ b/trycpp.c
@@ -0,0 +1,7 @@
+void main()
+{
+#ifdef NeXT
+  printf("nextstep\n"); exit(0);
+#endif
+  printf("unknown\n"); exit(0);
+}
diff --git a/trydrent.c b/trydrent.c
new file mode 100644 (file)
index 0000000..c778176
--- /dev/null
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <dirent.h>
+
+void foo()
+{
+  DIR *dir;
+  struct dirent *d;
+}
diff --git a/tryflock.c b/tryflock.c
new file mode 100644 (file)
index 0000000..8c8aa76
--- /dev/null
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/file.h>
+#include <fcntl.h>
+
+void main()
+{
+  flock(0,LOCK_EX | LOCK_UN | LOCK_NB);
+}
diff --git a/trylsock.c b/trylsock.c
new file mode 100644 (file)
index 0000000..fbce408
--- /dev/null
@@ -0,0 +1,4 @@
+main()
+{
+  ;
+}
diff --git a/trymkffo.c b/trymkffo.c
new file mode 100644 (file)
index 0000000..0b119c6
--- /dev/null
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include <sys/stat.h>
+
+void main()
+{
+  mkfifo("temp-trymkffo",0);
+}
diff --git a/trynpbg1.c b/trynpbg1.c
new file mode 100644 (file)
index 0000000..9d5f80b
--- /dev/null
@@ -0,0 +1,26 @@
+#include "select.h"
+#include "open.h"
+#include "fifo.h"
+
+#define FN "temp-trynpbg1.fifo"
+
+void main()
+{
+  int flagbug;
+  struct timeval instant;
+  fd_set rfds;
+  flagbug = 0;
+  if (fifo_make(FN,0600) != -1) {
+    close(0);
+    if (open_read(FN) == 0) {
+      FD_ZERO(&rfds);
+      FD_SET(0,&rfds);
+      instant.tv_sec = instant.tv_usec = 0;
+      if (select(1,&rfds,(fd_set *) 0,(fd_set *) 0,&instant) > 0)
+        flagbug = 1;
+    }
+    unlink(FN);
+  }
+  _exit(!flagbug);
+}
diff --git a/tryrsolv.c b/tryrsolv.c
new file mode 100644 (file)
index 0000000..fbce408
--- /dev/null
@@ -0,0 +1,4 @@
+main()
+{
+  ;
+}
diff --git a/trysalen.c b/trysalen.c
new file mode 100644 (file)
index 0000000..731a109
--- /dev/null
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+void foo()
+{
+  struct sockaddr sa;
+  sa.sa_len = 0;
+}
diff --git a/trysgact.c b/trysgact.c
new file mode 100644 (file)
index 0000000..263cb21
--- /dev/null
@@ -0,0 +1,10 @@
+#include <signal.h>
+
+void main()
+{
+  struct sigaction sa;
+  sa.sa_handler = 0;
+  sa.sa_flags = 0;
+  sigemptyset(&sa.sa_mask);
+  sigaction(0,&sa,(struct sigaction *) 0);
+}
diff --git a/trysgprm.c b/trysgprm.c
new file mode 100644 (file)
index 0000000..ed28857
--- /dev/null
@@ -0,0 +1,10 @@
+#include <signal.h>
+
+void main()
+{
+  sigset_t ss;
+  sigemptyset(&ss);
+  sigaddset(&ss,SIGCHLD);
+  sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0);
+}
diff --git a/tryshsgr.c b/tryshsgr.c
new file mode 100644 (file)
index 0000000..807e15d
--- /dev/null
@@ -0,0 +1,14 @@
+void main()
+{
+  short x[4];
+  x[0] = x[1] = 1;
+  if (getgroups(1,x) == 0) if (setgroups(1,x) == -1) _exit(1);
+  if (getgroups(1,x) == -1) _exit(1);
+  if (x[1] != 1) _exit(1);
+  x[1] = 2;
+  if (getgroups(1,x) == -1) _exit(1);
+  if (x[1] != 2) _exit(1);
+  _exit(0);
+}
diff --git a/trysysel.c b/trysysel.c
new file mode 100644 (file)
index 0000000..f6ed055
--- /dev/null
@@ -0,0 +1,8 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h> /* SVR4 silliness */
+
+void foo()
+{
+  ;
+}
diff --git a/trysyslog.c b/trysyslog.c
new file mode 100644 (file)
index 0000000..4b99afc
--- /dev/null
@@ -0,0 +1,9 @@
+#include <sys/types.h>
+#include <sys/time.h>
+#include <syslog.h>
+
+main()
+{
+  openlog("foo",0,LOG_MAIL);
+  syslog(0,"foo");
+}
diff --git a/tryulong32.c b/tryulong32.c
new file mode 100644 (file)
index 0000000..a108076
--- /dev/null
@@ -0,0 +1,11 @@
+void main()
+{
+  unsigned long u;
+  u = 1;
+  u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+  u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+  u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+  u += u; u += u; u += u; u += u; u += u; u += u; u += u; u += u;
+  if (!u) _exit(0);
+  _exit(1);
+}
diff --git a/tryvfork.c b/tryvfork.c
new file mode 100644 (file)
index 0000000..21387e4
--- /dev/null
@@ -0,0 +1,4 @@
+void main()
+{
+  vfork();
+}
diff --git a/trywaitp.c b/trywaitp.c
new file mode 100644 (file)
index 0000000..7e73bfa
--- /dev/null
@@ -0,0 +1,7 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+
+void main()
+{
+  waitpid(0,0,0);
+}
diff --git a/uint32.h1 b/uint32.h1
new file mode 100644 (file)
index 0000000..6599aa0
--- /dev/null
+++ b/uint32.h1
@@ -0,0 +1,6 @@
+#ifndef UINT32_H
+#define UINT32_H
+
+typedef unsigned int uint32;
+
+#endif
diff --git a/uint32.h2 b/uint32.h2
new file mode 100644 (file)
index 0000000..716430d
--- /dev/null
+++ b/uint32.h2
@@ -0,0 +1,6 @@
+#ifndef UINT32_H
+#define UINT32_H
+
+typedef unsigned long uint32;
+
+#endif
diff --git a/wait.3 b/wait.3
new file mode 100644 (file)
index 0000000..8c41f4b
--- /dev/null
+++ b/wait.3
@@ -0,0 +1,93 @@
+.TH wait 3
+.SH NAME
+wait \- check child process status
+.SH SYNTAX
+.B #include <wait.h>
+
+int \fBwait_nohang\fP(&\fIwstat\fR);
+.br
+int \fBwait_stop\fP(&\fIwstat\fR);
+.br
+int \fBwait_stopnohang\fP(&\fIwstat\fR);
+.br
+int \fBwait_pid\fP(&\fIwstat\fR,\fIpid\fR);
+
+int \fBwait_exitcode\fP(\fIwstat\fR);
+.br
+int \fBwait_crashed\fP(\fIwstat\fR);
+.br
+int \fBwait_stopped\fP(\fIwstat\fR);
+.br
+int \fBwait_stopsig\fP(\fIwstat\fR);
+
+int \fIpid\fR;
+.br
+int \fIwstat\fR;
+.SH DESCRIPTION
+.B wait_nohang
+looks for zombies (child processes that have exited).
+If it sees a zombie,
+it eliminates the zombie,
+puts the zombie's exit status into
+.IR wstat ,
+and returns the zombie's process ID.
+If there are several zombies,
+.B wait_nohang
+picks one.
+If there are children but no zombies,
+.B wait_nohang
+returns 0.
+If there are no children,
+.B wait_nohang
+returns -1,
+setting
+.B errno
+appropriately.
+
+.B wait_stopnohang
+is similar to
+.BR wait_nohang ,
+but it also looks for children that have stopped.
+
+.B wait_stop
+is similar to
+.BR wait_stopnohang ,
+but if there are children it will pause waiting for one of them
+to stop or exit.
+
+.B wait_pid
+waits for child process
+.I pid
+to exit.
+It eliminates any zombie that shows up in the meantime,
+discarding the exit status.
+
+.B wait_stop
+and
+.B wait_pid
+retry upon
+.BR error_intr .
+.SH "STATUS PARSING"
+If the child stopped,
+.B wait_stopped
+is nonzero;
+.B wait_stopsig
+is the signal that caused the child to stop.
+
+If the child exited by crashing,
+.B wait_stopped
+is zero;
+.B wait_crashed
+is nonzero.
+
+If the child exited normally,
+.B wait_stopped
+is zero;
+.B wait_crashed
+is zero;
+and
+.B wait_exitcode
+is the child's exit code.
+.SH "SEE ALSO"
+wait(2),
+error(3)
diff --git a/wait.h b/wait.h
new file mode 100644 (file)
index 0000000..cdb77c3
--- /dev/null
+++ b/wait.h
@@ -0,0 +1,14 @@
+#ifndef WAIT_H
+#define WAIT_H
+
+extern int wait_pid();
+extern int wait_nohang();
+extern int wait_stop();
+extern int wait_stopnohang();
+
+#define wait_crashed(w) ((w) & 127)
+#define wait_exitcode(w) ((w) >> 8)
+#define wait_stopsig(w) ((w) >> 8)
+#define wait_stopped(w) (((w) & 127) == 127)
+
+#endif
diff --git a/wait_nohang.c b/wait_nohang.c
new file mode 100644 (file)
index 0000000..bea2774
--- /dev/null
@@ -0,0 +1,12 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "haswaitp.h"
+
+int wait_nohang(wstat) int *wstat;
+{
+#ifdef HASWAITPID
+  return waitpid(-1,wstat,WNOHANG);
+#else
+  return wait3(wstat,WNOHANG,(struct rusage *) 0);
+#endif
+}
diff --git a/wait_pid.c b/wait_pid.c
new file mode 100644 (file)
index 0000000..8dde830
--- /dev/null
@@ -0,0 +1,13 @@
+#include <sys/types.h>
+#include <sys/wait.h>
+#include "error.h"
+
+/* restriction: you must not care about any other child. */
+int wait_pid(wstat,pid) int *wstat; int pid;
+{
+  int r;
+  do
+    r = wait(wstat);
+  while ((r != pid) && ((r != -1) || (errno == error_intr)));
+  return r;
+}
diff --git a/warn-auto.sh b/warn-auto.sh
new file mode 100644 (file)
index 0000000..36d2313
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+# WARNING: This file was auto-generated. Do not edit!
diff --git a/warn-shsgr b/warn-shsgr
new file mode 100644 (file)
index 0000000..37c351e
--- /dev/null
@@ -0,0 +1,3 @@
+Oops. Your getgroups() returned 0, and setgroups() failed; this means
+that I can't reliably do my shsgr test. Please either ``make'' as root
+or ``make'' while you're in one or more supplementary groups.