| 1 | Please note that this file is not called ``Internet Mail For Dummies.'' |
| 2 | It _records_ my thoughts on various issues. It does not _explain_ them. |
| 3 | Paragraphs are not organized except by section. The required background |
| 4 | varies wildly from one paragraph to the next. |
| 5 | |
| 6 | In this file, ``sendmail'' means Allman's creation; ``sendmail-clone'' |
| 7 | means the program in this package. |
| 8 | |
| 9 | |
| 10 | 1. Security |
| 11 | |
| 12 | There are lots of interesting remote denial-of-service attacks on any |
| 13 | mail system. A long-term solution is to insist on prepayment for |
| 14 | unauthorized resource use. The tricky technical problem is to make the |
| 15 | prepayment enforcement mechanism cheaper than the expected cost of the |
| 16 | attacks. (For local denial-of-service attacks it's enough to be able to |
| 17 | figure out which user is responsible.) |
| 18 | |
| 19 | qmail-send's log was originally designed for profiling. It subsequently |
| 20 | sprouted some tracing features. However, there's no way to verify |
| 21 | securely that a particular message came from a particular local user; |
| 22 | how do you know the recipient is telling you the truth about the |
| 23 | contents of the message? With QUEUE_EXTRA it'd be possible to record a |
| 24 | one-way hash of each outgoing message, but a user who wants to send |
| 25 | ``bad'' mail can avoid qmail entirely. |
| 26 | |
| 27 | I originally decided on security grounds not to put qmail advertisements |
| 28 | into SMTP responses: advertisements often act as version identifiers. |
| 29 | But this problem went away when I found a stable qmail URL. |
| 30 | |
| 31 | As qmail grows in popularity, the mere knowledge that rcpthosts is so |
| 32 | easily available will deter people from setting up unauthorized MXs. |
| 33 | (I've never seen an unauthorized MX, but I can imagine that it would be |
| 34 | rather annoying.) Note that, unlike the bat book checkcompat() kludge, |
| 35 | rcpthosts doesn't interfere with mailing lists. |
| 36 | |
| 37 | qmail-start doesn't bother with tty dissociation. On some old machines |
| 38 | this means that random people can send tty signals to the qmail daemons. |
| 39 | That's a security flaw in the job control subsystem, not in qmail. |
| 40 | |
| 41 | The resolver library isn't too bloated (before 4.9.4, at least), but it |
| 42 | uses stdio, which _is_ bloated. Reading /etc/resolv.conf costs lots of |
| 43 | memory in each qmail-remote process. So it's tempting to incorporate a |
| 44 | smaller resolver library into qmail. (Bonus: I'd avoid system-specific |
| 45 | problems with old resolvers.) The problem is that I'd then be writing a |
| 46 | fundamentally insecure library. I'd no longer be able to blame the BIND |
| 47 | authors and vendors for the fact that attackers can easily use DNS to |
| 48 | steal mail. Solution: insist that the resolver run on the same host; the |
| 49 | kernel can guarantee the security of low-numbered 127.0.0.1 UDP ports. |
| 50 | |
| 51 | NFS is the primary enemy of security partitioning under UNIX. Here's the |
| 52 | story. Sun knew from the start that NFS was completely insecure. It |
| 53 | tried to hide that fact by disallowing root access over NFS. Intruders |
| 54 | nevertheless broke into system after system, first obtaining bin access |
| 55 | and then obtaining root access. Various people thus decided to compound |
| 56 | Sun's error and build a wall between root and all other users: if all |
| 57 | system files are owned by root, and if there are no security holes other |
| 58 | than NFS, someone who breaks in via NFS won't be able to wipe out the |
| 59 | operating system---he'll merely be able to wipe out all user files. This |
| 60 | clueless policy means that, for example, all the qmail users have to be |
| 61 | replaced by root. See what I mean by ``enemy''? ... Basic NFS comments: |
| 62 | Aside from the cryptographic problem of having hosts communicate |
| 63 | securely, it's obvious that there's an administrative problem of mapping |
| 64 | client uids to server uids. If a host is secure and under your control, |
| 65 | you shouldn't have to map anything. If a host is under someone else's |
| 66 | control, you'll want to map his uids to one local account; it's his |
| 67 | client's job to decide which of his users get to talk NFS in the first |
| 68 | place. Sun's original map---root to nobody, everyone else left alone--- |
| 69 | is, as far as I can tell, always wrong. |
| 70 | |
| 71 | |
| 72 | 2. Injecting mail locally (qmail-inject, sendmail-clone) |
| 73 | |
| 74 | RFC 822 section 3.4.9 prohibits certain visual effects in headers, and |
| 75 | the 822bis draft prohibits even more. qmail-inject could enforce these |
| 76 | absurd restrictions, but why waste the time? If you will suffer from |
| 77 | someone sending you ``flash mail,'' go find a better mail reader. |
| 78 | |
| 79 | qmail-inject's ``Cc: recipient list not shown: ;'' successfully stops |
| 80 | sendmail from adding Apparently-To. Unfortunately, old versions of |
| 81 | sendmail will append a host name. This wasn't fixed until sendmail 8.7. |
| 82 | How many years has it been since RFC 822 came out? |
| 83 | |
| 84 | sendmail discards duplicate addresses. This has probably resulted in |
| 85 | more lost and stolen mail over the years than the entire Chicago branch |
| 86 | of the United States Postal Service. The qmail system delivers messages |
| 87 | exactly as it's told to do. Along the same lines: qmail-inject is both |
| 88 | unable and unwilling to support anything like sendmail's (default) |
| 89 | nometoo option. Of course, a list manager could support nometoo. |
| 90 | |
| 91 | There should be a mechanism in qmail-inject that does for envelope |
| 92 | recipients what Return-Path does for the envelope sender. Then |
| 93 | qmail-inject -n could print the recipients. |
| 94 | |
| 95 | Should qmail-inject bounce messages with no recipients? Should there be |
| 96 | an option for this? If it stays as is (accept the message), qmail-inject |
| 97 | could at least avoid invoking qmail-queue. |
| 98 | |
| 99 | It is possible to extract non-unique Message-IDs out of qmail-inject. |
| 100 | Here's how: stop qmail-inject before it gets to the third line of |
| 101 | main(), then wait until the pids wrap around, then restart qmail-inject |
| 102 | and blast the message through, then start another qmail-inject with the |
| 103 | same pid in the same second. I'm not sure how to fix this without |
| 104 | system-supplied sequence numbers. (Of course, the user could just type |
| 105 | in his own non-unique Message-IDs.) |
| 106 | |
| 107 | The bat book says: ``Rules that hide hosts in a domain should be applied |
| 108 | only to sender addresses.'' Recipient masquerading works fine with |
| 109 | qmail. None of sendmail's pitfalls apply, basically because qmail has a |
| 110 | straight paper path. |
| 111 | |
| 112 | I predicted that I would receive some pressure to make up for the |
| 113 | failings of MUA writers who don't understand the concept of reliability. |
| 114 | (``Like, duh, you mean I'm supposed to check the sendmail exit code?'') |
| 115 | I was right. |
| 116 | |
| 117 | |
| 118 | 3. Receiving mail from the network (tcp-env, qmail-smtpd) |
| 119 | |
| 120 | qmail-smtpd doesn't allow privacy-invading commands like VRFY and EXPN. |
| 121 | If you really want to publish such information, use a mechanism that |
| 122 | legitimate users actually know about, such as fingerd or httpd. |
| 123 | |
| 124 | RFC 1123 says that VRFY and EXPN are important to track down cross-host |
| 125 | mailing list loops. With Delivered-To, mailing list loops do no damage, |
| 126 | _and_ one of the list administrators gets a bounce message that shows |
| 127 | exactly how the loop occurred. Solve the problem, not the symptom. |
| 128 | |
| 129 | Should dns.c make special allowances for 127.0.0.1/localhost? |
| 130 | |
| 131 | badmailfrom (like 8BITMIME) is a waste of code space. |
| 132 | |
| 133 | In theory a MAIL or RCPT argument can contain unquoted LFs. In practice |
| 134 | there are a huge number of clients that terminate commands with just LF, |
| 135 | even if they use CR properly inside DATA. |
| 136 | |
| 137 | |
| 138 | 4. Adding messages to the queue (qmail-queue) |
| 139 | |
| 140 | Should qmail-queue try to make sure enough disk space is free in |
| 141 | advance? When qmail-queue is invoked by qmail-local or (with ESMTP) |
| 142 | qmail-smtpd or qmail-qmtpd or qmail-qmqpd, it could be told a size in |
| 143 | advance. I wish UNIX had an atomic allocate-disk-space routine... |
| 144 | |
| 145 | The qmail.h interface (reflecting the qmail-queue interface, which in |
| 146 | turn reflects the current queue file structure) is constitutionally |
| 147 | incapable of handling an address that contains a 0 byte. I can't imagine |
| 148 | that this will be a problem. |
| 149 | |
| 150 | Should qmail-queue not bother queueing a message with no recipients? |
| 151 | |
| 152 | |
| 153 | 5. Handling queued mail (qmail-send, qmail-clean) |
| 154 | |
| 155 | The queue directory must be local. Mounting it over NFS is extremely |
| 156 | dangerous---not that this stops people from running sendmail that way! |
| 157 | Diskless hosts should use mini-qmail instead. |
| 158 | |
| 159 | Queue reliability demands that single-byte writes be atomic. This is |
| 160 | true for a fixed-block filesystem such as UFS, and for a logging |
| 161 | filesystem such as LFS. |
| 162 | |
| 163 | qmail-send uses 8 bytes of memory per queued message. Double that for |
| 164 | reallocation. (Fix: use a small forest of heaps; i.e., keep several |
| 165 | prioqs.) Double again for buddy malloc()s. (Fix: be clever about the |
| 166 | heap sizes.) 32 bytes is worrisome, but not devastating. Even on my |
| 167 | disk-heavy memory-light machine, I'd run out of inodes long before |
| 168 | running out of memory. |
| 169 | |
| 170 | Some mail systems organize the queue by host. This is pointless as a |
| 171 | means of splitting up the queue directory. The real issue is what to do |
| 172 | when you suddenly find out that a host is up. For local SLIP/PPP links |
| 173 | you know in advance which hosts need this treatment, so you can handle |
| 174 | them with virtualdomains and serialmail. |
| 175 | |
| 176 | For the old queue structure I implemented recipient list compression: |
| 177 | if mail goes out to a giant mailing list, and most of the recipients are |
| 178 | delivered, make a new, compressed, todo list. But this really isn't |
| 179 | worth the effort: it saves only a tiny bit of CPU time. |
| 180 | |
| 181 | qmail-send doesn't have any notions of precedence, priority, fairness, |
| 182 | importance, etc. It handles the queue in first-seen-first-served order. |
| 183 | One could put a lot of work into doing something different, but that |
| 184 | work would be a waste: given the triggering mechanism and qmail's |
| 185 | deferral strategy, it is exceedingly rare for the queue to contain more |
| 186 | than one deliverable message at any given moment. |
| 187 | |
| 188 | Exception: Even with all the concurrency tricks, qmail-send can end up |
| 189 | spending a few minutes on a mailing list with thousands of remote |
| 190 | entries. A user might send a new message to a remote address in the |
| 191 | meantime. The simplest way to handle this would be to put big messages |
| 192 | on a separate channel. |
| 193 | |
| 194 | qmail-send will never start a pass for a job that it already has. This |
| 195 | means that, if one delivery takes longer than the retry interval, the |
| 196 | next pass will be delayed. I implemented the opposite strategy for the |
| 197 | old queue structure. Some hassles: mark() had to understand how job |
| 198 | input was buffered; every new delivery had to check whether the same |
| 199 | mpos in the same message was already being done. |
| 200 | |
| 201 | Some things that qmail-send does synchronously: queueing a bounce |
| 202 | message; doing a cleanup via qmail-clean; classifying and rewriting all |
| 203 | the addresses in a new message. As usual, making these asynchronous |
| 204 | would require some housekeeping, but could speed things up a bit. |
| 205 | (I'm willing to assume POSIX waitpid() for asynchronous bounces; putting |
| 206 | an unbounded buffer into wait_pid() for the sake of NeXTSTEP 3 is not |
| 207 | worthwhile.) |
| 208 | |
| 209 | Disk I/O is a bottleneck; UFS is reliable but it isn't fast. A good |
| 210 | logging filesystem offers much better performance, but logging |
| 211 | filesystems aren't widely available. Solution: Keep a journal, separate |
| 212 | from the queue, adequate to rebuild the queue (with at worst some |
| 213 | duplicate deliveries). Compress the journal. This would dramatically |
| 214 | reduce total disk I/O. |
| 215 | |
| 216 | Bounce aggregation is a dubious feature. Bounce records aren't |
| 217 | crashproof; there can be a huge delay between a failure and a bounce; |
| 218 | the resulting bounce format is unnecessarily complicated. I'm tempted to |
| 219 | scrap the bounce directory and send one bounce for each failing |
| 220 | recipient, with appropriate modifications in the accompanying text. |
| 221 | |
| 222 | qmail-stop implementation: setuid to UID_SEND; kill -TERM -1. Or run |
| 223 | qmail-start under an external service controller, such as supervise; |
| 224 | that's why it runs in the foreground. |
| 225 | |
| 226 | The readdir() interface hides I/O errors. Lower-level interfaces would |
| 227 | lead me into a thicket of portability problems. I'm really not sure what |
| 228 | to do about this. Of course, a hard I/O error means that mail is toast, |
| 229 | but a soft I/O error shouldn't cause any trouble. |
| 230 | |
| 231 | job_open() or pass_dochan() could be paranoid about the same id,channel |
| 232 | already being open; but, since messdone() is so paranoid, the worst |
| 233 | possible effect of a bug along these lines would be double delivery. |
| 234 | |
| 235 | Mathematical amusement: The optimal retry schedule is essentially, |
| 236 | though not exactly, independent of the actual distribution of message |
| 237 | delay times. What really matters is how much cost you assign to retries |
| 238 | and to particular increases in latency. qmail's current quadratic retry |
| 239 | schedule says that an hour-long delay in a day-old message is worth the |
| 240 | same as a ten-minute delay in an hour-old message; this doesn't seem so |
| 241 | unreasonable. |
| 242 | |
| 243 | Insider information: AOL retries their messages every five minutes for |
| 244 | three days straight. Hmmm. |
| 245 | |
| 246 | |
| 247 | 6. Sending mail through the network (qmail-rspawn, qmail-remote) |
| 248 | |
| 249 | Are there any hosts, anywhere, whose mailers are bogged down by huge |
| 250 | messages to multiple recipients at a single host? For typical hosts, |
| 251 | multiple RCPTs per SMTP aren't an ``efficiency feature''; they're a |
| 252 | _slowness_ feature. Separate SMTP transactions have much lower latency. |
| 253 | |
| 254 | I've heard three complaints about bandwidth use from masochists sending |
| 255 | messages through a modem through a smarthost to thousands of users--- |
| 256 | without sublists! They can get much better performance with QMQP. |
| 257 | |
| 258 | In the opposite direction: It's tempting to remove the @host part of the |
| 259 | qmail-remote recip argument. Or at least avoid double-dns_cname. |
| 260 | |
| 261 | There are lots of reasons that qmail-rspawn should take a more active |
| 262 | role in qmail-remote's activities. It should call separate programs to |
| 263 | do (1) MX lookups, (2) SMTP connections, (3) QMTP connections. (But this |
| 264 | wouldn't be so important if the DNS library didn't burn so much memory.) |
| 265 | |
| 266 | I bounce ambiguous MXs. (An ``ambiguous MX'' is a best-preference MX |
| 267 | record sending me mail for a host that I don't recognize as local.) |
| 268 | Automatically treating ambiguous MXs as local is incompatible with my |
| 269 | design decision to keep local delivery working when the network goes |
| 270 | down. It puts more faith in DNS than DNS deserves. Much better: Have |
| 271 | your MX records generated automatically from control/locals. |
| 272 | |
| 273 | If I successfully connect to an MX host but it temporarily refuses to |
| 274 | accept the message, I give up and put the message back into the queue. |
| 275 | But several documents seem to suggest that I should try further MX |
| 276 | records. What are they thinking? My approach deals properly with downed |
| 277 | hosts, hosts that are unreachable through a firewall, and load |
| 278 | balancing; what else do people use multiple MX records for? |
| 279 | |
| 280 | Currently qmail-remote sends data in 1024-byte buffers. Perhaps it |
| 281 | should try to take account of the MTU. |
| 282 | |
| 283 | Perhaps qmail-remote should allocate a fixed amount of DNS/connect() |
| 284 | time across any number of MXs; this idea is due to Mark Delany. |
| 285 | |
| 286 | RFC 821 doesn't say what it means by ``text.'' qmail-remote assumes that |
| 287 | the server's reply text doesn't contain bare LFs. |
| 288 | |
| 289 | RFC 821 and RFC 1123 prohibit host names in MAIL FROM and RCPT TO from |
| 290 | being aliases. qmail-remote, like sendmail, rewrites aliases in RCPT; |
| 291 | people who don't list aliases in control/locals or sendmail's Cw are |
| 292 | implicitly relying on this conversion. It is course quite silly for an |
| 293 | internal DNS detail to have such an effect on mail delivery, but that's |
| 294 | how the Internet works. On the other hand, the compatibility arguments |
| 295 | do not apply to MAIL FROM. qmail-remote no longer bothers with CNAME |
| 296 | lookups for the envelope sender host. |
| 297 | |
| 298 | |
| 299 | 7. Delivering mail locally (qmail-lspawn, qmail-local) |
| 300 | |
| 301 | qmail-local doesn't support comsat. comsat is a pointless abomination. |
| 302 | Use qbiff if you want that kind of notification. |
| 303 | |
| 304 | The getpwnam() interface hides I/O errors. Solution: qmail-pw2u. |
| 305 | |
| 306 | |
| 307 | 8. sendmail V8's new features |
| 308 | |
| 309 | sendmail-8.8.0/doc/op/op.me includes a list of big improvements of |
| 310 | sendmail 8.8.0 over sendmail 5.67. Here's how qmail stacks up against |
| 311 | each of those improvements. (Of course, qmail has its own improvements, |
| 312 | but that's not the point of this list.) |
| 313 | |
| 314 | Connection caching, MX piggybacking: Nope. (Profile. Don't speculate.) |
| 315 | |
| 316 | Response to RCPT command is fast: Yup. |
| 317 | |
| 318 | IP addresses show up in Received lines: Yup. |
| 319 | |
| 320 | Self domain literal is properly handled: Yup. |
| 321 | |
| 322 | Different timeouts for QUIT, RCPT, etc.: No, just a single timeout. |
| 323 | |
| 324 | Proper <> handling, route-address pruning: Yes, but not configurable. |
| 325 | |
| 326 | ESMTP support: Yup. (Server-side, including PIPELINING.) |
| 327 | |
| 328 | 8-bit clean: Yup. (Including server-side 8BITMIME support; same as |
| 329 | sendmail with the 8 option.) |
| 330 | |
| 331 | Configurable user database: Yup. |
| 332 | |
| 333 | BIND support: Yup. |
| 334 | |
| 335 | Keyed files: Yes, in fastforward. |
| 336 | |
| 337 | 931/1413/Ident/TAP: Yup. |
| 338 | |
| 339 | Correct 822 address list parsing: Yup. (Note that sendmail still has |
| 340 | some major problems with quoting.) |
| 341 | |
| 342 | List-owner handling: Yup. |
| 343 | |
| 344 | Dynamic header allocation: Yup. |
| 345 | |
| 346 | Minimum number of disk blocks: Yes, via tunefs -m. (Or quotas; the right |
| 347 | setup has qmailq with a small quota, qmails with a larger quota, so that |
| 348 | qmail-send always has room to work.) |
| 349 | |
| 350 | Checkpointing: Yes, but not configurable---qmail always checkpoints. |
| 351 | |
| 352 | Error message configuration: Nope. |
| 353 | |
| 354 | GECOS matching: Not directly, but easy to hook in. |
| 355 | |
| 356 | Hop limit configuration: No. (qmail's limit is 100 hops. qmail offers |
| 357 | automatic loop protection much more advanced than hop counting.) |
| 358 | |
| 359 | MIME error messages: No. (qmail uses QSBMF error messages, which are |
| 360 | much easier to parse.) |
| 361 | |
| 362 | Forward file path: Yes, via /etc/passwd. |
| 363 | |
| 364 | Incoming SMTP configuration: Yes, via inetd or tcpserver. |
| 365 | |
| 366 | Privacy options: Yes, but they're not options. |
| 367 | |
| 368 | Best-MX mangling: Nope. See section 6 for further discussion. |
| 369 | |
| 370 | 7-bit mangling: Nope. qmail always uses 8 bits. |
| 371 | |
| 372 | Support for up to 20 MX records: Yes, and more. qmail has no limits |
| 373 | other than memory. |
| 374 | |
| 375 | Correct quoting of name-and-address headers: Yup. |
| 376 | |
| 377 | VRFY and EXPN now different: Nope. qmail always hides this information. |
| 378 | |
| 379 | Multi-word classes, deferred macro expansion, separate envelope/header |
| 380 | $g processing, separate per-mailer envelope and header processing, new |
| 381 | command line flags, new configuration lines, new mailer flags, new |
| 382 | macros: These are sendmail-specific; they wouldn't even make sense for |
| 383 | qmail. For example, _of course_ qmail handles envelopes and headers |
| 384 | separately; they're almost entirely different objects! |
| 385 | |
| 386 | |
| 387 | 9. Miscellany |
| 388 | |
| 389 | sendmail-clone and qsmhook are too bletcherous to be documented. (The |
| 390 | official replacement for qsmhook is preline, together with the |
| 391 | qmail-command environment variables.) |
| 392 | |
| 393 | I've considered making install atomic, but this is very difficult to do |
| 394 | right, and pointless if it isn't done right. |
| 395 | |
| 396 | RN suggests automatically putting together a reasonable set of lines for |
| 397 | /etc/passwd. I perceive this as getting into the adduser business, which |
| 398 | is worrisome: I'll be lynched the first time I screw up somebody's |
| 399 | passwd file. This should be left to OS-specific installation scripts. |
| 400 | |
| 401 | The BSD 4.2 inetd didn't allow a username. I think I can safely forget |
| 402 | about this. (DS notes that the username works under Ultrix even though |
| 403 | it's undocumented.) |
| 404 | |
| 405 | I should clean up the bput/put choices. |
| 406 | |
| 407 | Some of the stralloc_0()s indicate that certain lower-level routines |
| 408 | should grok stralloc. |
| 409 | |
| 410 | qmail assumes that all times are positive; that pid_t, time_t and ino_t |
| 411 | fit into unsigned long; that gid_t fits into int; that the character set |
| 412 | is ASCII; and that all pointers are interchangeable. Do I care? |
| 413 | |
| 414 | The bat book justifies sendmail's insane line-splitting mechanism by |
| 415 | pointing out that it might be useful for ``a 40-character braille |
| 416 | print-driving program.'' C'mon, guys, is that your best excuse? |
| 417 | |
| 418 | qmail's mascot is a dolphin. |