(In this list, ``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 is oriented towards profiling, not tracing. This means,
for example, that qmail-send doesn't bother scanning for or recording
Message-IDs. I can see some value in being able to state with confidence
that a particular message did pass through the machine---in particular,
being able to verify that it came from a particular local user---but
this requires a one-way hash of each message, not just the Message-ID.
It also requires getting rid of sendmail everywhere, since sendmail
irrevocably screws with other people's headers.

Given a local address, qmail needs to figure out whether the user part
of the address is a proper target of mail, and, if so, where that user
stores his files and what his uid is. There should, therefore, be (1) a
database that says whether an account is a proper target of mail; (2) a
database mapping accounts to home directories; and (3) a database
mapping accounts to uids. /etc/passwd manages to handle (2) and (3),
though it's disgusting that a program should have to drag in a password
library for something totally unrelated to passwords. Right now qmail
handles (1) by the following heuristics: a root account is not a proper
target of mail; an account that doesn't own ~account is not a proper
target of mail; everyone else is okay.

There should be another database saying whether an account is allowed to
run programs of its own choosing. You may not want random users running
programs on your mail server via .qmail files, for example. Right now
qmail doesn't have any checks along these lines. sendmail uses
/etc/shells, which is already massively overloaded. (And a silly concept
in the first place. We wouldn't need to ``protect'' users from choosing
nonfunctional login shells if a user could specify a shell upon login.
Of course, this only applies to users who are allowed to run programs.)

Should tcp-env include tcpd-style paranoia?

RN suggests putting a qmail advertisement into smtpgreeting. I have
decided against this on security grounds: such advertisements are likely
to act as version identifiers.

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.

I should add another list, relayclients, of TCPREMOTEIP names for which
rcpthosts isn't checked. Other policy-based filtering features might
also be desirable.

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.

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, but it doesn't stop sendmail from
getting confused. Several times I've seen someone's sendmail incorrectly
append a host name. (Ah, I see that this was fixed in 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.

There should be a qmail-inject option to print out the envelope. This
should not be incorporated into -n; people in vi should be able to type
``:%!qmail-inject -n'' and then send the result. On second thought, this
suggests that the envelope sender should be incorporated into -n, while
the envelope recipients should not. Perhaps there should be a mechanism
that does for recipients what Return-Path does for the sender.

There should be several medium-level interfaces above token.c.

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.

Have I missed any important sendmail-clone options? It would be
inconceivable to clone the entire sendmail interface, but it's easy
enough to provide the small set of options that MUAs use.

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.

The 80 in token.c means a maximum line length of 78. Perhaps this should
be an argument.

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
support 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.

qmail-smtpd could do an A lookup on every incoming recipient address; if
it gives TCPLOCALIP, replace it with TCPLOCALHOST. This would mean that
people who use multinaming rather than aliasing (example: relay1.uu.net
and mail.uu.net) don't have to worry about control/locals configuration
any more. Easy problem: RFC 1123 says that I have to accept the address
if the lookup fails with a soft error. Solution: Ignore the idiocies in
RFC 1123. Hard problem: The original domain could have MX records,
including a better-preference MX than us. If I insist on snarfing the
mail here, some people will have to change how their MXs are
structured... Why do people use multinaming, anyway? [sigh]

Should dns.c make special allowances for 127.0.0.1/localhost?


4. Adding messages to the queue (qmail-queue)

Currently qmail-queue treats ENOSPC like any other write error. I'm sure
this produces rather uninformative error messages, which I should fix.

Should qmail-queue try to make sure enough disk space is free in
advance? When qmail-queue is invoked by qmail-alias or (with ESMTP)
qmail-smtpd or a future qmail-qmtpd, it could be told a size in advance.
I wish UNIX had an allocate-disk-space routine... 

The qqtalk 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?

qmail-queue could go to more effort to clean up on errors. (Unsafe to
clean up on timeout, for the obvious reasons.)


5. Handling queued mail (qmail-send, qmail-clean)

qmail-send is the only long-running program that reads control files.
Perhaps it should automatically watch for changes in the control files,
or at least have some mechanism (like HUP) to reread them. On the other
hand, restart code is very easy to screw up, as recent versions of named
illustrate. Anyway, since TERM is safe, who cares? ... RN suggests a
qmail-config-edit program that does the following: kill qmail-send, wait
for it to stop, edit config files (atomically---qmail-smtpd and
qmail-inject can still run at any moment), and restart qmail-send.

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, a bastard version of qmail-queue, and qmail-remote.
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.

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. As a means of splitting up
the queue directory, this is not too effective. The real issue here is
how to deal with hosts that are down. Example 1 is SLIP/PPP. Example 2
is RFC 1123's the-host-must-be-awake-if-it-sends-us-mail, though I find
it inconceivable that this would happen with any noticeable frequency.
The common feature of these examples is that we suddenly know that
certain queued mail messages can be sent _right now_.

I should implement recipient list compression. The case to keep in mind:
mail goes out on a giant mailing list; most of the recipients are
delivered on the first run; a few stragglers take lots and lots of runs.
I'm not sure how giant the mailing list would have to be before this
wastes any noticeable CPU time. (I actually implemented compression for
the old queue structure.)

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 hundreds 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 queue run. 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.

Should qmail-send yell if it is blocked for more than a few seconds?

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.)

It's beginning to look like fsync() will be 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.

It would be easy to handle the % hack inside qmail-send. Interface:
control/percenthack is a list of hosts H where we do X%Y@H -> X@Y. Here
Y would have to be fully qualified. I can put this in as an option, but
I'm certainly not going to make it the default. (It's much more tempting
to make rcpthosts the default.)

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.

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.

I could add a total concurrency limit: e.g., up to 20 local deliveries,
up to 30 remote deliveries, but no more than 35 deliveries in any case.

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.

RDM suggests being able to rewrite entire addresses. This should happen
before any other rewrites; control/recipientmap, perhaps.

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.

In the opposite direction: It's tempting to remove the @host part of the
qmail-remote recip argument. Or at least avoid double-dns_canon.

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 kill itself if DNS or connect() is slow. Or
allocate a fixed amount of 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-alias)

qmail-alias 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. Not sure what to do here.


8. sendmail V8's new features

sendmail-8.7.3/doc/op/op.me includes a list of big improvements of
sendmail 8.7.3 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. However, even without these,
qmail handles the queue an order of magnitude faster than sendmail.

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. Who
cares?

Proper <> handling, route-address pruning: Yes, but not configurable.

ESMTP support: Yup. (Server-side.)

8-bit clean: Yup. (Including server-side 8BITMIME support; same as
sendmail with the 8 option.)

Udb: Nope.

BIND support: Yup.

Keyed files: Not directly, but easy to hook in.

931/1413/Ident/TAP: Yup.

Parsing bug fixes: No need; qmail-inject's RFC 822 compliance is perfect.
sendmail still has 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: Nope. 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: Not directly, but easy to hook in.

Incoming SMTP configuration: Yes, via inetd.

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: qmail doesn't support these at all.

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.

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. But I'll think about it.

The BSD 4.2 inetd didn't allow a username. I'm not sure whether I should
mention this in INSTALL. (DS notes that the username works under Ultrix
even though it's undocumented.)

To whoever invented SIOCGIFCONF: Wtf do you expect people to do if the
buffer fills up?

I should go to more effort to report ``good'' error messages, even in
the one-time setup scripts.

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.

Kyle Jones suggests that mailing list subscription managers should have
a three-way handshake, to prevent person A from subscribing person B to
a mailing list.

Conventional wisdom says that you can't safely mix select() with
SIGCHLD (or other signals): the SIGCHLD might go off while select() is
starting, too early to interrupt it, to late to change its timeout.
Solution: the self-pipe trick. Maintain a self-pipe and select for
readability on the input. Inside SIGCHLD, write a byte (non-blocking,
just in case) to the output. Done. (Of course, the Right Thing would be
to have fork() return a file descriptor, not a PID.) Although this isn't
used in qmail with the new lspawn/rspawn structure, I figure I'll keep
this note here in case someone else finds the trick useful.

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 logo is a shark.
