maddy

Fork https://github.com/foxcpp/maddy

git clone git://git.lin.moe/go/maddy.git

  1## Design goals
  2
  3- **Make it easy to deploy.**
  4  Minimal configuration changes should be required to get a typical mail server
  5  running. Though, it is important to avoid making guesses for a
  6  "zero-configuration". A wrong guess is worse than no guess.
  7
  8- **Provide 80% of needed components.**
  9  E-mail has evolved into a huge mess. With a single package to do one thing, it
 10  quickly turns into a maintenance nightmare. Put all stuff mail server
 11  typically needs into a single package. Though, leave controversial or highly
 12  opinionated stuff out, don't force people to do things our way
 13  (see next point).
 14
 15- **Interoperate with existing software.**
 16  Implement (de-facto) standard protocols not only for clients but also for
 17  various server-side helper software (content filters, etc).
 18
 19- **Be secure but interoperable.**
 20  Verify DKIM signatures by default, use DMRAC policies by default, etc. This
 21  makes default setup as secure as possible while maintaining reasonable
 22  interoperability. Though, users can configure maddy to be stricter.
 23
 24- **Achieve flexibility through composability.**
 25  Allow connecting components in arbitrary ways instead of restricting users to
 26  predefined templates.
 27
 28- **Use Go concurrency features to the full extent.**
 29  Do as much I/O as possible in parallel to minimize latencies. It is silly to
 30  not do so when it is possible.
 31
 32## Design summary
 33
 34Here is a summary of how things are organized in maddy in general. It explains
 35things from the developer perspective and is meant to be used as an
 36introduction by the new developers/contributors. It is recommended to read
 37user documentation to understand how things work from the user perspective as
 38well.
 39
 40- User documentation: [maddy.conf(5)](docs/man/maddy.5.scd)
 41- Design rationale: [Comments on design (Wiki)][1]
 42
 43There are components called "modules". They are represented by objects
 44implementing the module.Module interface. Each module gets its unique name.
 45The function used to create a module instance is saved with this name as a key
 46into the global map called "modules registry".
 47
 48Whenever module needs another module for some functionality, it references it
 49using a configuration directive with a matcher that internally calls
 50`modconfig.ModuleFromNode`. That function looks up the module "constructor" in
 51the registry, calls it with corresponding arguments, checks whether the
 52returned module satisfies the needed interfaces and then initializes it.
 53
 54Alternatively, if configuration uses &-syntax to reference existing
 55configuration block, `ModuleFromNode` simply looks it up in the global instances
 56registry. All modules defined the configuration as a separate top-level blocks
 57are created before main initialization and are placed in the instances registry
 58where they can be looked up as mentioned before.
 59
 60Top-level defined module instances are initialized (`Init` method) lazily as
 61they are required by other modules. 'smtp' and 'imap' modules follow a special
 62initialization path, so they are always initialized directly.
 63
 64## Error handling
 65
 66Familiarize yourself with the `github.com/foxcpp/maddy/framework/exterrors`
 67package and make sure you have the following for returned errors:
 68- SMTP status information (smtp\_code, smtp\_enchcode, smtp\_msg fields)
 69  - SMTP message text should contain a generic description of the error
 70    condition without any details to prevent accidental disclosure of the
 71    server configuration details.
 72- `Temporary() == true` for temporary errors (see `exterrors.WithTemporary`)
 73- Field that includes the module name
 74
 75The easiest way to get all of these is to use `exterrors.SMTPError`.
 76Put the original error into the `Err` field, so it can be inspected using
 77`errors.Is`, `errors.Unwrap`, etc. Put the module name into `CheckName` or
 78`TargetName`. Add any additional context information using the `Misc` field.
 79Note, the SMTP status code overrides the result of `exterrors.IsTemporary()`
 80for that error object, so set it using `exterrors.SMTPCode` that uses
 81`IsTemporary` to select between two codes.
 82
 83If the error you are wrapping contains details in its structure fields (like
 84`*net.OpError`) - copy these values into `Misc` map, put the underlying error
 85object (`net.OpError.Err`, for example) into the `Err` field.
 86Avoid using `Reason` unless you are sure you can provide the error message
 87better than the `Err.Error()` or `Err` is `nil`.
 88
 89Do not attempt to add a SMTP status information for every single possible
 90error. Use `exterrors.WithFields` with basic information for errors you don't
 91expect. The SMTP client will get the "Internal server error" message and this
 92is generally the right thing to do on unexpected errors.
 93
 94### Goroutines and panics
 95
 96If you start any goroutines - make sure to catch panics to make sure severe
 97bugs will not bring the whole server down.
 98
 99## Adding a check
100
101"Check" is a module that inspects the message and flags it as spam or rejects
102it altogether based on some condition.
103
104The skeleton for the stateful check module can be found in
105`internal/check/skeleton.go`.  Throw it into a file in
106`internal/check/check_name` directory and start ~~breaking~~ extending it.
107
108If you don't need any per-message state, you can use `StatelessCheck` wrapper.
109See `check/dns` directory for a working example.
110
111Here are some guidelines to make sure your check works well:
112- RTFM, docs will tell you about any caveats.
113- Don't share any state _between_ messages, your code will be executed in
114  parallel.
115- Use `github.com/foxcpp/maddy/check.FailAction` to select behavior on check
116  failures. See other checks for examples on how to use it.
117- You can assume that order of check functions execution is as follows:
118  `CheckConnection`, `CheckSender`, `CheckRcpt`, `CheckBody`.
119
120## Adding a modifier
121
122"Modifier" is a module that can modify some parts of the message data.
123
124Note, currently this is not possible to modify the body contents, only header
125can be modified.
126
127Structure of the modifier implementation is similar to the structure of check
128implementation, check `modify/replace\_addr.go` for a working example.
129
130[1]: https://github.com/foxcpp/maddy/wiki/Dev:-Comments-on-design