2016-09-26

Is the internet ready for DMARC with p=reject?

No.

DMARC is an email policy that builds on DKIM and SPF to provide a way for email senders to declare: "All email from this domain comes from this set of servers (SPF), and is signed using these public keys (DKIM)."

It's nice and well that we are finally able to do that, but DMARC comes with 3 modes of operation: p=reject, p=quarantine, and p=none. These modes suggest what a recipient should do if an incoming email doesn't match the sender domain's DMARC criteria, either due to signature failure (DKIM) or incorrect sending server (SPF). The default mode, p=none, boils down to "do whatever, maybe this helps you guess if the email is spam". The mode p=quarantine treats the email as spam, and p=reject is supposed to cause the message to be rejected at the point of ESMTP delivery.

Well, I tried p=reject, and it kinda works. Mostly. Except in the following situations:
  • If you send to any mailing lists from your domain. Your messages may be DKIM signed, but when the mail server forwards them, they will be sent by a mail server that does not have authority to send messages for your domain according to your SPF criteria. Message headers may also be modified so that the DKIM signature does not validate. This will cause your messages to the mailing list to be rejected by many subscribers at the point of ESMTP delivery. This in turn may cause the mailing list to frown on you.
  • If you send to anyone with a multi-layered mail server setup. In this case, your message is received correctly by the front mail server, which is configured to forward it to a back mail server. You cannot control this because it's a setup specific to the recipient and unknown to you in advance. But the back mail server is poorly configured to verify DMARC criteria, and rejects the message because it violates your SPF policy – from the perspective of the back mail server, it is coming from the wrong IP. The message may then be deleted silently, or sorted into trash.
This means you pretty much can't safely run a mail server with DMARC policy set to p=reject. For the same reasons that p=reject is not safe, p=quarantine also makes no sense. There's not much difference between causing your outgoing mail to be bounced and/or deleted, or causing it to land in spam.

DKIM and SPF are good ideas, but it's unfortunate that the policy has to be "none".

2016-09-08

Lock-step refactoring

This may seem obvious, but I am biased to avoid saying things that are obvious to me, and then things don't get said that are worth saying.

A nice thing about statically typed languages, like C++, is how you can use the compiler to help refactor.

Code often needs to be refactored. If it is not, it gets harder to fix issues and implement new features. When this happens, the temptation arises to throw the code away and write new code, but this is almost always a mistake. The correct process is to refactor code that works, to disentangle anything that needs disentangling, so that improvement becomes easier.

When there is existing code you can use, you should only ever write new code if you're willing to go through the entire years-long process of how it needs to work, and why it needs to work that way. Otherwise, use existing code, and keep improving it.

Refactoring is scary. It is scary because you have this hairy code, and you're concerned that any change you make to it will break it.

Behold, the beauty of a statically typed language. By leaning on the compiler, you can refactor in such a way that code will not compile until it is fully updated – and ideally, correct:
  1. Start with code that compiles and passes any unit tests you might have. Write unit tests if there aren't any. Focus on functionality you're worried about, and want to fix and/or preserve.
  2. For each modification you want to make:
    1. Find a way to make this modification so that the code will not compile unless all parts that need to take this modification into account are updated.
    2. Repeat the build as many times as necessary to identify all parts you need to update.
  3. When the code builds, if you used the compiler to aid you correctly, you should have found all the places that required updates. Unit tests should not fail because of missing an update. They should only fail if an update you made was incorrect.
For example:
  • If you change parameters accepted by a function, make sure no possible call using the previous signature will compile with the new signature.
  • If this cannot be achieved, rename the function, so that references to the old name will not build.
  • If you change the behavior of a function in a way that requires callers to update, change its signature in a breaking way, or change its name.
  • If you change the rules regarding a widely used data member, change its name.
  • If you change the rules regarding a widely used type, change its name.
Aspire to perform all changes in such a way that, if you're not 100% done, the code will not even compile. Don't make changes that allow the code to compile if you're only 1%, or 40%, or 99% done.