Full Code of emersion/maddy for AI

master 837b1b8a7761 cached
448 files
1.8 MB
569.4k tokens
2069 symbols
1 requests
Download .txt
Showing preview only (1,981K chars total). Download the full file or copy to clipboard to get everything.
Repository: emersion/maddy
Branch: master
Commit: 837b1b8a7761
Files: 448
Total size: 1.8 MB

Directory structure:
gitextract_bng2bnky/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.md
│   │   ├── config.yml
│   │   └── feature-request.md
│   ├── SECURITY.md
│   ├── releases.md
│   └── workflows/
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── .mkdocs.yml
├── .version
├── AGENTS.md
├── COPYING
├── Dockerfile
├── HACKING.md
├── README.md
├── build.sh
├── cmd/
│   ├── README.md
│   ├── maddy/
│   │   └── main.go
│   ├── maddy-pam-helper/
│   │   ├── README.md
│   │   ├── maddy.conf
│   │   ├── main.c
│   │   ├── main.go
│   │   ├── pam.c
│   │   └── pam.h
│   └── maddy-shadow-helper/
│       ├── README.md
│       └── main.go
├── config.go
├── contrib/
│   ├── README.md
│   └── kubernetes/
│       └── chart/
│           ├── .helmignore
│           ├── Chart.yaml
│           ├── README.md
│           ├── files/
│           │   ├── aliases
│           │   └── maddy.conf
│           ├── templates/
│           │   ├── NOTES.txt
│           │   ├── _helpers.tpl
│           │   ├── configmap.yaml
│           │   ├── deployment.yaml
│           │   ├── pvc.yaml
│           │   ├── service.yaml
│           │   ├── serviceaccount.yaml
│           │   └── tests/
│           │       └── test-connection.yaml
│           └── values.yaml
├── directories.go
├── directories_docker.go
├── dist/
│   ├── README.md
│   ├── apparmor/
│   │   └── dev.foxcpp.maddy
│   ├── fail2ban/
│   │   ├── filter.d/
│   │   │   ├── maddy-auth.conf
│   │   │   └── maddy-dictonary-attack.conf
│   │   └── jail.d/
│   │       ├── maddy-auth.conf
│   │       └── maddy-dictonary-attack.conf
│   ├── install.sh
│   ├── logrotate.d/
│   │   └── maddy
│   ├── systemd/
│   │   ├── maddy.service
│   │   └── maddy@.service
│   └── vim/
│       ├── ftdetect/
│       │   └── maddy-conf.vim
│       ├── ftplugin/
│       │   └── maddy-conf.vim
│       └── syntax/
│           └── maddy-conf.vim
├── docs/
│   ├── docker.md
│   ├── faq.md
│   ├── index.md
│   ├── internals/
│   │   ├── quirks.md
│   │   ├── specifications.md
│   │   ├── sqlite.md
│   │   └── unicode.md
│   ├── man/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── maddy.1.scd
│   │   └── prepare_md.py
│   ├── multiple-domains.md
│   ├── reference/
│   │   ├── auth/
│   │   │   ├── dovecot_sasl.md
│   │   │   ├── external.md
│   │   │   ├── ldap.md
│   │   │   ├── netauth.md
│   │   │   ├── pam.md
│   │   │   ├── pass_table.md
│   │   │   ├── plain_separate.md
│   │   │   └── shadow.md
│   │   ├── blob/
│   │   │   ├── fs.md
│   │   │   └── s3.md
│   │   ├── checks/
│   │   │   ├── actions.md
│   │   │   ├── authorize_sender.md
│   │   │   ├── command.md
│   │   │   ├── dkim.md
│   │   │   ├── dnsbl.md
│   │   │   ├── milter.md
│   │   │   ├── misc.md
│   │   │   ├── rspamd.md
│   │   │   └── spf.md
│   │   ├── config-syntax.md
│   │   ├── endpoints/
│   │   │   ├── imap.md
│   │   │   ├── openmetrics.md
│   │   │   └── smtp.md
│   │   ├── global-config.md
│   │   ├── modifiers/
│   │   │   ├── dkim.md
│   │   │   └── envelope.md
│   │   ├── modules.md
│   │   ├── smtp-pipeline.md
│   │   ├── storage/
│   │   │   ├── imap-filters.md
│   │   │   └── imapsql.md
│   │   ├── table/
│   │   │   ├── auth.md
│   │   │   ├── chain.md
│   │   │   ├── email_localpart.md
│   │   │   ├── email_with_domain.md
│   │   │   ├── file.md
│   │   │   ├── regexp.md
│   │   │   ├── sql_query.md
│   │   │   └── static.md
│   │   ├── targets/
│   │   │   ├── queue.md
│   │   │   ├── remote.md
│   │   │   └── smtp.md
│   │   ├── tls-acme.md
│   │   └── tls.md
│   ├── seclevels.md
│   ├── third-party/
│   │   ├── dovecot.md
│   │   ├── mailman3.md
│   │   ├── rspamd.md
│   │   └── smtp-servers.md
│   ├── tutorials/
│   │   ├── alias-to-remote.md
│   │   ├── building-from-source.md
│   │   ├── pam.md
│   │   └── setting-up.md
│   └── upgrading.md
├── framework/
│   ├── address/
│   │   ├── doc.go
│   │   ├── norm.go
│   │   ├── norm_test.go
│   │   ├── rfc6531.go
│   │   ├── rfc6531_test.go
│   │   ├── split.go
│   │   ├── split_test.go
│   │   ├── validation.go
│   │   └── validation_test.go
│   ├── buffer/
│   │   ├── buffer.go
│   │   ├── bytesreader.go
│   │   ├── file.go
│   │   └── memory.go
│   ├── cfgparser/
│   │   ├── env.go
│   │   ├── imports.go
│   │   ├── parse.go
│   │   └── parse_test.go
│   ├── config/
│   │   ├── config.go
│   │   ├── directories.go
│   │   ├── endpoint.go
│   │   ├── endpoint_test.go
│   │   ├── lexer/
│   │   │   ├── LICENSE.APACHE
│   │   │   ├── README.md
│   │   │   ├── dispenser.go
│   │   │   ├── dispenser_test.go
│   │   │   ├── lexer.go
│   │   │   ├── lexer_test.go
│   │   │   └── parse.go
│   │   ├── map.go
│   │   ├── map_test.go
│   │   ├── module/
│   │   │   ├── check_action.go
│   │   │   ├── interfaces.go
│   │   │   └── modconfig.go
│   │   └── tls/
│   │       ├── client.go
│   │       ├── general.go
│   │       └── server.go
│   ├── container/
│   │   ├── container.go
│   │   ├── lifetime.go
│   │   └── registry.go
│   ├── dns/
│   │   ├── debugflags.go
│   │   ├── dnssec.go
│   │   ├── dnssec_test.go
│   │   ├── idna.go
│   │   ├── norm.go
│   │   ├── override.go
│   │   └── resolver.go
│   ├── exterrors/
│   │   ├── dns.go
│   │   ├── exterrors.go
│   │   ├── fields.go
│   │   ├── smtp.go
│   │   └── temporary.go
│   ├── future/
│   │   ├── future.go
│   │   └── future_test.go
│   ├── hooks/
│   │   └── hooks.go
│   ├── log/
│   │   ├── log.go
│   │   ├── orderedjson.go
│   │   ├── output.go
│   │   ├── syslog.go
│   │   ├── syslog_stub.go
│   │   ├── writer.go
│   │   └── zap.go
│   ├── logparser/
│   │   ├── parse.go
│   │   └── parse_test.go
│   ├── module/
│   │   ├── auth.go
│   │   ├── blob_store.go
│   │   ├── check.go
│   │   ├── delivery_target.go
│   │   ├── imap_filter.go
│   │   ├── modifier.go
│   │   ├── module.go
│   │   ├── module_specific_data.go
│   │   ├── modules/
│   │   │   ├── dummy.go
│   │   │   └── modules.go
│   │   ├── msgmetadata.go
│   │   ├── mxauth.go
│   │   ├── partial_delivery.go
│   │   ├── storage.go
│   │   ├── table.go
│   │   └── tls_loader.go
│   └── resource/
│       ├── netresource/
│       │   ├── dup.go
│       │   ├── fd.go
│       │   ├── listen.go
│       │   └── tracker.go
│       ├── resource.go
│       ├── singleton.go
│       └── tracker.go
├── go.mod
├── go.sum
├── internal/
│   ├── README.md
│   ├── auth/
│   │   ├── auth.go
│   │   ├── auth_test.go
│   │   ├── dovecot_sasl/
│   │   │   └── dovecot_sasl.go
│   │   ├── external/
│   │   │   ├── externalauth.go
│   │   │   └── helperauth.go
│   │   ├── ldap/
│   │   │   └── ldap.go
│   │   ├── netauth/
│   │   │   └── netauth.go
│   │   ├── pam/
│   │   │   ├── module.go
│   │   │   ├── pam.c
│   │   │   ├── pam.go
│   │   │   ├── pam.h
│   │   │   └── pam_stub.go
│   │   ├── pass_table/
│   │   │   ├── hash.go
│   │   │   ├── table.go
│   │   │   └── table_test.go
│   │   ├── plain_separate/
│   │   │   ├── plain_separate.go
│   │   │   └── plain_separate_test.go
│   │   ├── sasl.go
│   │   ├── sasl_test.go
│   │   ├── sasllogin/
│   │   │   └── sasllogin.go
│   │   └── shadow/
│   │       ├── module.go
│   │       ├── read.go
│   │       ├── shadow.go
│   │       └── verify.go
│   ├── authz/
│   │   ├── lookup.go
│   │   └── normalization.go
│   ├── check/
│   │   ├── authorize_sender/
│   │   │   └── authorize_sender.go
│   │   ├── command/
│   │   │   └── command.go
│   │   ├── dkim/
│   │   │   ├── dkim.go
│   │   │   └── dkim_test.go
│   │   ├── dns/
│   │   │   ├── dns.go
│   │   │   └── dns_test.go
│   │   ├── dnsbl/
│   │   │   ├── common.go
│   │   │   ├── common_test.go
│   │   │   ├── dnsbl.go
│   │   │   └── dnsbl_test.go
│   │   ├── milter/
│   │   │   ├── milter.go
│   │   │   └── milter_test.go
│   │   ├── requiretls/
│   │   │   └── requiretls.go
│   │   ├── rspamd/
│   │   │   └── rspamd.go
│   │   ├── skeleton.go
│   │   ├── spf/
│   │   │   └── spf.go
│   │   └── stateless_check.go
│   ├── cli/
│   │   ├── app.go
│   │   ├── clitools/
│   │   │   ├── clitools.go
│   │   │   ├── termios.go
│   │   │   └── termios_stub.go
│   │   ├── ctl/
│   │   │   ├── appendlimit.go
│   │   │   ├── hash.go
│   │   │   ├── imap.go
│   │   │   ├── imapacct.go
│   │   │   ├── moduleinit.go
│   │   │   └── users.go
│   │   └── extflag.go
│   ├── dmarc/
│   │   ├── dmarc.go
│   │   ├── evaluate.go
│   │   ├── evaluate_test.go
│   │   ├── verifier.go
│   │   └── verifier_test.go
│   ├── dsn/
│   │   └── dsn.go
│   ├── endpoint/
│   │   ├── dovecot_sasld/
│   │   │   ├── dovecot_sasl.go
│   │   │   └── mech_info.go
│   │   ├── imap/
│   │   │   └── imap.go
│   │   ├── openmetrics/
│   │   │   └── om.go
│   │   └── smtp/
│   │       ├── date.go
│   │       ├── metrics.go
│   │       ├── session.go
│   │       ├── smtp.go
│   │       ├── smtp_test.go
│   │       ├── smtputf8_test.go
│   │       ├── submission.go
│   │       └── submission_test.go
│   ├── imap_filter/
│   │   ├── command/
│   │   │   └── command.go
│   │   └── group.go
│   ├── libdns/
│   │   ├── acmedns.go
│   │   ├── alidns.go
│   │   ├── cloudflare.go
│   │   ├── digitalocean.go
│   │   ├── gandi.go
│   │   ├── gcore.go
│   │   ├── googleclouddns.go
│   │   ├── hetzner.go
│   │   ├── leaseweb.go
│   │   ├── metaname.go
│   │   ├── namecheap.go
│   │   ├── namedotcom.go
│   │   ├── provider_module.go
│   │   ├── rfc2136.go
│   │   ├── route53.go
│   │   └── vultr.go
│   ├── limits/
│   │   ├── limiters/
│   │   │   ├── bucket.go
│   │   │   ├── concurrency.go
│   │   │   ├── limiters.go
│   │   │   ├── multilimit.go
│   │   │   └── rate.go
│   │   └── limits.go
│   ├── modify/
│   │   ├── dkim/
│   │   │   ├── dkim.go
│   │   │   ├── dkim_test.go
│   │   │   ├── keys.go
│   │   │   └── keys_test.go
│   │   ├── group.go
│   │   ├── replace_addr.go
│   │   └── replace_addr_test.go
│   ├── msgpipeline/
│   │   ├── bench_test.go
│   │   ├── bodynonatomic_test.go
│   │   ├── check_group.go
│   │   ├── check_runner.go
│   │   ├── check_test.go
│   │   ├── config.go
│   │   ├── config_test.go
│   │   ├── dmarc_test.go
│   │   ├── metrics.go
│   │   ├── modifier_test.go
│   │   ├── module.go
│   │   ├── msgpipeline.go
│   │   ├── msgpipeline_test.go
│   │   ├── objname.go
│   │   └── regress_test.go
│   ├── proxy_protocol/
│   │   └── proxy_protocol.go
│   ├── smtpconn/
│   │   ├── pool/
│   │   │   └── pool.go
│   │   ├── smtpconn.go
│   │   ├── smtpconn_test.go
│   │   └── smtputf8_test.go
│   ├── sqlite/
│   │   ├── is.go
│   │   ├── modernc_sqlite3.go
│   │   ├── no_sqlite3.go
│   │   └── sqlite3.go
│   ├── storage/
│   │   ├── blob/
│   │   │   ├── fs/
│   │   │   │   ├── fs.go
│   │   │   │   └── fs_test.go
│   │   │   ├── s3/
│   │   │   │   ├── s3.go
│   │   │   │   └── s3_test.go
│   │   │   ├── test_blob.go
│   │   │   └── test_blob_nosqlite.go
│   │   └── imapsql/
│   │       ├── bench_test.go
│   │       ├── delivery.go
│   │       ├── external_blob_store.go
│   │       ├── imapsql.go
│   │       └── maddyctl.go
│   ├── table/
│   │   ├── chain.go
│   │   ├── email_localpart.go
│   │   ├── email_with_domain.go
│   │   ├── file.go
│   │   ├── file_test.go
│   │   ├── identity.go
│   │   ├── regexp.go
│   │   ├── sql_query.go
│   │   ├── sql_query_test.go
│   │   ├── sql_table.go
│   │   └── static.go
│   ├── target/
│   │   ├── delivery.go
│   │   ├── queue/
│   │   │   ├── metrics.go
│   │   │   ├── queue.go
│   │   │   ├── queue_test.go
│   │   │   ├── timewheel.go
│   │   │   └── timewheel_test.go
│   │   ├── received.go
│   │   ├── remote/
│   │   │   ├── connect.go
│   │   │   ├── dane.go
│   │   │   ├── dane_delivery_test.go
│   │   │   ├── dane_test.go
│   │   │   ├── debugflags.go
│   │   │   ├── metrics.go
│   │   │   ├── mxauth_test.go
│   │   │   ├── policy_group.go
│   │   │   ├── remote.go
│   │   │   ├── remote_test.go
│   │   │   └── security.go
│   │   ├── skeleton.go
│   │   └── smtp/
│   │       ├── sasl.go
│   │       ├── sasl_test.go
│   │       ├── smtp_downstream.go
│   │       ├── smtp_downstream_test.go
│   │       └── smtputf8_test.go
│   ├── testutils/
│   │   ├── bench_delivery.go
│   │   ├── buffer.go
│   │   ├── check.go
│   │   ├── filesystem.go
│   │   ├── logger.go
│   │   ├── modifier.go
│   │   ├── multitable.go
│   │   ├── smtp_server.go
│   │   ├── table.go
│   │   └── target.go
│   ├── tls/
│   │   ├── acme/
│   │   │   └── acme.go
│   │   ├── file.go
│   │   └── self_signed.go
│   └── updatepipe/
│       ├── backend.go
│       ├── pubsub/
│       │   ├── pq.go
│       │   └── pubsub.go
│       ├── pubsub_pipe.go
│       ├── serialize.go
│       ├── unix_pipe.go
│       └── update_pipe.go
├── maddy.conf
├── maddy.conf.docker
├── maddy.go
├── maddy_debug.go
├── signal.go
├── signal_nonposix.go
├── systemd.go
├── systemd_nonlinux.go
└── tests/
    ├── README.md
    ├── basic_test.go
    ├── build_cover.sh
    ├── conn.go
    ├── cover_test.go
    ├── dovecot_sasl_test.go
    ├── dovecot_sasld_test.go
    ├── ghsa_5835_4gvc_32pc_test.go
    ├── gocovcat.go
    ├── golangci-noisy.yml
    ├── imap_test.go
    ├── imapsql_test.go
    ├── issue327_test.go
    ├── limits_test.go
    ├── lmtp_test.go
    ├── modules_test.go
    ├── mta_test.go
    ├── multiple_domains_test.go
    ├── reload_non_unix.go
    ├── reload_test.go
    ├── reload_unix.go
    ├── replace_addr_test.go
    ├── run.sh
    ├── smtp_autobuffer_test.go
    ├── smtp_test.go
    ├── stress_test.go
    ├── t.go
    └── testdata/
        ├── check_command.sh
        ├── testing+addHeader@maddy.test.hdr
        └── testing+reject@maddy.test.exit

================================================
FILE CONTENTS
================================================

================================================
FILE: .dockerignore
================================================
testdata/
cmd/maddy/maddy
maddy
tests/maddy.cover


================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

[*.{scd,go}]
indent_style = tab
indent_size = 4

[*.yml]
indent_style = tab
indent_size = 2


================================================
FILE: .gitattributes
================================================
* text=auto eol=lf


================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Code of Merit

**1.** The project creators, lead developers, core team, constitute the managing
members of the project and have final say in every decision of the project,
technical or otherwise, including overruling previous decisions. There are no
limitations to this decisional power.

**2.** Contributions are an expected result of your membership on the project.
Don’t expect others to do your work or help you with your work forever.

**3.** All members have the same opportunities to seek any challenge they want
within the project.

**4.** Authority or position in the project will be proportional to the accrued
contribution. Seniority must be earned.

**5.** Software is evolutive: the better implementations must supersede lesser
implementations. Technical advantage is the primary evaluation metric.

**6.** This is a space for technical prowess; topics outside of the project will
not be tolerated.

**7.** Non technical conflicts will be discussed in a separate space. Disruption
of the project will not be allowed.

**8.** Individual characteristics, including but not limited to, body, sex,
sexual preference, race, language, religion, nationality, or political
preferences are irrelevant in the scope of the project and will not be taken
into account concerning your value or that of your contribution to the project.

**9.** Discuss or debate the idea, not the person.

**10.** There is no room for ambiguity: Ambiguity will be met with questioning;
further ambiguity will be met with silence. It is the responsibility of the
originator to provide requested context.

**11.** If something is illegal outside the scope of the project, it is illegal
in the scope of the project. This Code of Merit does not take precedence over
governing law.

**12.** This Code of Merit governs the technical procedures of the project not
the activities outside of it.

**13.** Participation on the project equates to agreement of this Code of Merit.

**14.** No objectives beyond the stated objectives of this project are relevant
to the project. Any intent to deviate the project from its original purpose of
existence will constitute grounds for remedial action which may include
expulsion from the project.

This document is the Code of Merit
(<strike>`http://code-of-merit.org`</strike>), version 1.0.


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing Guidelines

Of course, we love our contributors. Thanks for spending time on making maddy
better.

## Reporting bugs

**Issue tracker is meant to be used only if you have a problem or a feature
request. If you just have some questions about maddy - prefer to use the
[IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1).**

- Provide log files, preferably with 'debug' directive set.
- Provide the exact steps to reproduce the issue.
- Provide the example message that causes the error, if applicable.
- "Too much information is better than not enough information".

Issues without enough information will be ignored and possibly closed.
Take some time to be more useful.

See SECURITY.md for information on how to report vulnerabilities.

## Contributing Code

0. Use common sense.
1. Learn Git. Especially, what is `git rebase`. We may ask you to use it if
   needed.
2. Tell us that you are willing to work on an issue.
3. Fork the repo. Create a new branch based on `dev`, write your code. Open a
   PR.

Ask for advice if you are not sure. We don't bite.

maddy design summary and some recommendations are provided in
[HACKING.md](../HACKING.md) file.

## Commits

1. Prefix commit message with a package path if it affects only a single
   package. Omit `internal/` for brevity.
2. Provide reasoning for details in the source code itself (via comments),
   provide reasoning for high-level decisions in the commit message.
3. Make sure every commit builds & passes tests. Otherwise `git bisect` becomes
   unusable.

## Git workflow

`dev` branch includes the in-development version for the next feature release.
It is based on commit of the latest stable release and is merged into `master`
on release via fast-forward. Unlike `master`, `dev` **is not a protected branch
and may get force-pushes**.

`master` branch contains the latest stable release and is frozen between
releases.

`fix-X.Y` are temporary branches containing backported security fixes.
They are based on the commit of the corresponding stable release and exist
while the corresponding release is maintained. A `fix-*` branch is not created
for the latest release. Changes are added to these branches by cherry-picking
needed commits from the `dev` branch.


================================================
FILE: .github/ISSUE_TEMPLATE/bug-report.md
================================================
---
name: Bug report
about: If you think something is broken
title: Bug report
labels: bug
assignees: ''

---

# Describe the bug

What do you think is wrong?

# Steps to reproduce

# Log files

Use a service like hastebin.com or attach a file if it is big

# Configuration file

Located in /etc/maddy/maddy.conf by default, don't forget to remove DB passwords
and other security-related stuff.

# Environment information

* maddy version: ?


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
contact_links:
  - name: Questions 
    url: "https://github.com/foxcpp/maddy/discussions/new?category=q-a"
    about: "Use GitHub discussions for any questions"
  - name: IRC channel
    url: "https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1"
    about: "... or there is also an IRC channel for any discussions"


================================================
FILE: .github/ISSUE_TEMPLATE/feature-request.md
================================================
---
name: Feature request
about: If you would like to see a new feature in maddy.
title: Feature request
labels: new feature
assignees: ''

---

# Use case

What problem you are trying to solve?

Note alternatives you considered and why they are not useful.

# Your idea for a solution

How your solution would work in general?
Note that some overly complicated solutions may be rejected because maddy is
meant to be simple.

- [ ] I'm willing to help with the implementation


================================================
FILE: .github/SECURITY.md
================================================
# Security Policy

## Supported Versions

Two latest incompatible releases (e.g. 2.0.0 and 1.9.0).

Latest release gets all bug fixes, features, etc. Previous incompatible release
gets security fixes and fixes for problems that render software completely
unusable in certain configurations with no workaround.

## Reporting a Vulnerability

If you believe the vulnerabilitiy does have a big impact on existing
deployments - email `fox.cpp at disroot.org`, put "[maddy security]" in the
Subject.

Otherwise, open a public issue.


================================================
FILE: .github/releases.md
================================================
# Release preparation

1. Run linters, fix all simple warnings. If the behavior is intentional - add
`nolint` comment and explanation. If the warning is non-trviail to fix - open
an issue.
```
golangci-lint run
```

2. Run unit tests suite. Verify that all disabled tests are not related to
   serious problems and have corresponding issue open.
```
go test ./...
```

3. Run integration tests suite. Verify that all disabled tests are not related
   to serious problems and have corresponding issue open.
```
cd tests/
./run.sh
```

4. Write release notes.

5. Create PGP-signed Git tag and push it to GitHub (do not create a "release"
   yet).

5. Use environment configuration from maddy-repro bundle
   (https://foxcpp.dev/maddy-repro) to build release artifacts.

6. Create detached PGP signatures for artifacts using key
   3197BBD95137E682A59717B434BB2007081396F4.

7. Create sha256sums file for artifacts.

8. Create release on GitHub using the same text for
   release notes. Attach signed artifacts and sha256sums file.

9. Build the Docker container and push it to hub.docker.com.

10. Post a message on the sr.ht mailing list.


================================================
FILE: .github/workflows/release.yml
================================================
name: "Prepare release artifacts"

on:
  push:
    tags: [ "v*" ]

permissions:
  id-token: write
  contents: read
  attestations: write
  packages: write

jobs:
  artifact-builder-x86:
    name: "Prepare release artifacts (x86)"
    if: github.ref_type == 'tag'
    runs-on: ubuntu-latest
    container:
      image: "alpine:edge"
    steps:
      - uses: actions/checkout@v1 # v2 does not work with containers
      - name: "Install build dependencies"
        run: |
          apk add --no-cache gcc go zstd
      - name: "Create and package build tree"
        run: |
          ./build.sh --builddir ~/package-output/ --static build
          ver=$(cat .version)
          if [ "v$ver" != "${{github.ref_name}}" ]; then echo ".version does not match the Git tag"; exit 1; fi
          mv ~/package-output/ ~/maddy-$ver-x86_64-linux-musl
          cd ~
          tar c ./maddy-$ver-x86_64-linux-musl | zstd > ~/maddy-x86_64-linux-musl.tar.zst
          cd -
      - name: "Save source tree"
        run: |
          rm -rf .git
          ver=$(cat .version)
          cp -r . ~/maddy-$ver-src
          cd ~
          tar c ./maddy-$ver-src | zstd > ~/maddy-src.tar.zst
          cd -
      - name: "Upload source tree"
        uses: actions/upload-artifact@v4
        with:
          name: maddy-src.tar.zst
          path: '~/maddy-src.tar.zst'
          if-no-files-found: error
      - name: "Upload binary tree"
        uses: actions/upload-artifact@v4
        with:
          name: maddy-binary.tar.zst
          path: '~/maddy-x86_64-linux-musl.tar.zst'
          if-no-files-found: error
      - name: "Generate artifact attestation"
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: '~/maddy-x86_64-linux-musl.tar.zst'
  artifact-builder-arm:
    name: "Prepare release artifacts (aarch64)"
    if: github.ref_type == 'tag'
    runs-on: ubuntu-22.04-arm
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
      # Building in a Docker container is a workaround for the issue of
      # JavaScript-based GitHub Actions not being supported in Alpine
      # containers on the Arm64 platform. Otherwise, we could completely reuse
      # artifact-builder-x86 as a matrix job by running it on an Arm runner.
      - name: Build in Docker container
        run: |
          # Create Dockerfile for the build
          cat > Dockerfile << 'EOF'
          FROM alpine:edge
          RUN apk add --no-cache gcc go zstd musl-dev scdoc
          WORKDIR /build
          COPY . .
          RUN ./build.sh --builddir /package-output/ --static build && \
              ver=$(cat .version) && \
              if [ "v$ver" != "${{github.ref_name}}" ]; then echo ".version does not match the Git tag"; exit 1; fi && \
              mv /package-output/ /maddy-$ver-aarch64-linux-musl && \
              cd / && \
              tar c ./maddy-$ver-aarch64-linux-musl | zstd > /maddy-aarch64-linux-musl.tar.zst
          EOF
          # Build the image, create a temporary container and copy the artifact.
          docker build -t maddy-builder .
          container_id=$(docker create maddy-builder)
          docker cp $container_id:/maddy-aarch64-linux-musl.tar.zst .
          docker rm $container_id
      - name: Upload binary tree
        uses: actions/upload-artifact@v4
        with:
          name: maddy-binary-aarch64.tar.zst
          path: maddy-aarch64-linux-musl.tar.zst
          if-no-files-found: error
      - name: "Generate artifact attestation"
        uses: actions/attest-build-provenance@v2
        with:
          subject-path: 'maddy-aarch64-linux-musl.tar.zst'
  docker-builder:
    name: "Build & push Docker image"
    if: github.ref_type == 'tag'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: "Set up QEMU"
        uses: docker/setup-qemu-action@v1
        with:
          platforms: arm64
      - name: "Set up Docker Buildx"
        id: buildx
        uses: docker/setup-buildx-action@v3
      - name: "Login to Docker Hub"
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_PASSWORD }}
          logout: false
      - name: "Login to GitHub Container Registry"
        uses: docker/login-action@v3
        with:
          registry: "ghcr.io"
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}
          logout: false # https://news.ycombinator.com/item?id=28607735
      - name: "Generate container metadata"
        uses: docker/metadata-action@v5
        id: meta
        with:
          images: |
            foxcpp/maddy
            ghcr.io/foxcpp/maddy
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
          labels: |
            org.opencontainers.image.title=Maddy Mail Server
            org.opencontainers.image.documentation=https://maddy.email/docker/
            org.opencontainers.image.url=https://maddy.email
      - name: "Build and push"
        uses: docker/build-push-action@v6
        id: docker
        with:
          context: .
          platforms: linux/amd64 #,linux/arm64  Temporary disabled due to SIGSEGV in gcc.
          file: Dockerfile
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
      - name: "Generate container attestation"
        uses: actions/attest-build-provenance@v2
        with:
          subject-name: ghcr.io/foxcpp/maddy
          subject-digest: ${{ steps.docker.outputs.digest }}
          push-to-registry: true



================================================
FILE: .github/workflows/test.yml
================================================
name: "Testing"

on:
  push:
    branches: [ master, dev ]
    tags: [ "v*" ]
  pull_request:
    branches: [ master, dev ]

permissions:
  contents: read
  pull-requests: read
  checks: write

jobs:
  golangci:
    name: Lint
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version-file: 'go.mod'
      - name: "Install libpam"
        run: |
          sudo apt-get update
          sudo apt-get install -y libpam-dev
      - uses: golangci/golangci-lint-action@v9
        with:
          version: v2.11
  buildsh:
    name: "Verify build.sh"
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-go@v6
        with:
          go-version-file: 'go.mod'
      - name: "Install libpam"
        run: |
          sudo apt-get update
          sudo apt-get install -y libpam-dev
      - name: "Verify build.sh"
        run: |
          ./build.sh
          ./build.sh --destdir destdir/ install
          find destdir/
  test:
    name: "Build and test"
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v6
    - uses: actions/setup-go@v6
      with:
        go-version-file: 'go.mod'
    - name: "Install libpam"
      run: |
        sudo apt-get update
        sudo apt-get install -y libpam-dev
    - name: "Unit & module tests"
      run: |
        go test ./... -coverprofile=coverage.out -covermode=atomic
    - name: "Integration tests"
      run: |
        cd tests/
        ./run.sh


================================================
FILE: .gitignore
================================================
# gitignore.io
*.o
*.a
*.so
_obj
_test
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.exe~
*.test
*.prof
**/.envrc
**/.DS_Store

# Tests coverage
*.out

# Compiled binaries
cmd/maddy/maddy
cmd/maddy-*-helper/maddy-*-helper
/maddy

# Man pages
docs/man/*.1
docs/man/*.5

# Certificates and private keys.
*.pem
*.crt
*.key

# Some directories that may be created during test-runs
# in repo directory.
cmd/maddy/*mtasts-cache
cmd/maddy/*queue

build/

tests/maddy.cover
tests/maddy

.idea/


================================================
FILE: .golangci.yml
================================================
version: "2"
linters:
  enable:
  - errcheck
  - staticcheck
  - ineffassign
  - govet
  - unused
  - prealloc
  - unconvert
  - misspell
  - whitespace
  - nakedret
  - dogsled
  - copyloopvar
  - sqlclosecheck
  - testifylint
  - rowserrcheck
  - recvcheck
  settings:
    errcheck:
      disable-default-exclusions: false
formatters:
  enable:
  - goimports


================================================
FILE: .mkdocs.yml
================================================
site_name: maddy

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

theme: alb

markdown_extensions:
  - codehilite:
      guess_lang: false

nav:
  - faq.md
  - Tutorials:
    - tutorials/setting-up.md
    - tutorials/building-from-source.md
    - tutorials/alias-to-remote.md
    - tutorials/pam.md
  - Release builds: 'https://maddy.email/builds/'
  - multiple-domains.md
  - upgrading.md
  - seclevels.md
  - docker.md
  - Reference manual:
      - reference/modules.md
      - reference/global-config.md
      - reference/tls.md
      - reference/tls-acme.md
      - Endpoints configuration:
          - reference/endpoints/imap.md
          - reference/endpoints/smtp.md
          - reference/endpoints/openmetrics.md
      - IMAP storage:
          - reference/storage/imap-filters.md
          - reference/storage/imapsql.md
          - Blob storage:
            - reference/blob/fs.md
            - reference/blob/s3.md
      - reference/smtp-pipeline.md
      - SMTP targets:
          - reference/targets/queue.md
          - reference/targets/remote.md
          - reference/targets/smtp.md
      - SMTP checks:
          - reference/checks/actions.md
          - reference/checks/dkim.md
          - reference/checks/spf.md
          - reference/checks/milter.md
          - reference/checks/rspamd.md
          - reference/checks/dnsbl.md
          - reference/checks/command.md
          - reference/checks/authorize_sender.md
          - reference/checks/misc.md
      - SMTP modifiers:
          - reference/modifiers/dkim.md
          - reference/modifiers/envelope.md
      - Lookup tables (string translation):
          - reference/table/static.md
          - reference/table/regexp.md
          - reference/table/file.md
          - reference/table/sql_query.md
          - reference/table/chain.md
          - reference/table/email_localpart.md
          - reference/table/email_with_domain.md
          - reference/table/auth.md
      - Authentication providers:
          - reference/auth/pass_table.md
          - reference/auth/pam.md
          - reference/auth/shadow.md
          - reference/auth/external.md
          - reference/auth/ldap.md
          - reference/auth/dovecot_sasl.md
          - reference/auth/plain_separate.md
          - reference/auth/netauth.md
      - reference/config-syntax.md
  - Integration with software:
      - third-party/dovecot.md
      - third-party/smtp-servers.md
      - third-party/rspamd.md
      - third-party/mailman3.md
  - Internals:
    - internals/specifications.md
    - internals/unicode.md
    - internals/quirks.md
    - internals/sqlite.md


================================================
FILE: .version
================================================
0.9.4


================================================
FILE: AGENTS.md
================================================
# AGENTS.md — Maddy Mail Server

## Architecture

Maddy is a composable all-in-one mail server (MTA/MX/IMAP) written in Go. The core abstraction is the **module system**: every functional component (auth, storage, checks, targets, endpoints) implements `module.Module` from `framework/module/module.go` and registers itself via `module.Register(name, factory)` in an `init()` function.

- **`framework/`** — Stable, reusable packages (config parsing, module interfaces, address handling, error types, logging). Interfaces live here to avoid circular imports.
- **`internal/`** — All module implementations. Subdirectories map to module roles: `endpoint/` (protocol listeners), `target/` (delivery destinations), `auth/`, `check/` (message inspectors), `modify/` (header modifiers), `storage/`, `table/` (string→string lookups).
- **`maddy.go`** — Side-effect imports that pull all `internal/` modules into the binary, plus the `Run`/`moduleConfigure`/`RegisterModules` startup sequence.
- **`cmd/maddy/main.go`** — Thin entrypoint; imports root package for module registration, then calls `maddycli.Run()`.

Modules are wired together at runtime via `maddy.conf` configuration. Top-level blocks are lazily initialized through `module.Registry`. The **message pipeline** (`internal/msgpipeline/`) routes messages from endpoints through checks, modifiers, and to delivery targets based on sender/recipient matching rules.

## Build & Test

```sh
# Build (produces ./build/maddy by default):
./build.sh build

# Build with specific tags (e.g. for Docker):
./build.sh --tags "docker" build

# Unit tests (standard Go):
go test ./...

# Integration tests
cd tests && ./run.sh
```

The build embeds version via `-ldflags -X github.com/foxcpp/maddy.Version=...`. A C compiler is needed for SQLite support (`mattn/go-sqlite3`).

## Adding a New Module

1. Create a package under the appropriate `internal/` subdirectory (e.g. `internal/check/mycheck/`).
2. Implement `module.Module` plus the relevant role interface (`module.Check`, `module.DeliveryTarget`, `module.PlainAuth`, `module.Table`, etc.) from `framework/module/`.
3. Register in `init()`: `module.Register("check.mycheck", NewMyCheck)`. Use naming convention: `check.`, `target.`, `auth.`, `table.`, `modify.` prefixes.
4. Add a blank import `_ "github.com/foxcpp/maddy/internal/check/mycheck"` in `maddy.go`.
5. For checks: use the skeleton at `internal/check/skeleton.go` or `check.RegisterStatelessCheck` (see `internal/check/dns/` for a stateless example).

## Error Handling

Use `framework/exterrors` — not bare `fmt.Errorf`. Errors crossing module boundaries must carry:
- SMTP status info via `exterrors.SMTPError{Code, EnhancedCode, Message, CheckName/TargetName}`
- Temporary flag via `exterrors.WithTemporary`
- Module name field

Keep SMTP error messages generic (no server config details). Use `exterrors.WithFields` for unexpected errors. See `HACKING.md` for full guidelines.

## Key Conventions

- **No shared state between messages** — check/modifier code runs in parallel across messages.
- **Panic recovery** — any goroutine you spawn must recover panics to avoid crashing the server.
- **Address normalization** — domain parts must be U-labels with NFC normalization and case-folding. Use `framework/address.CleanDomain`.
- **Configuration parsing** — modules receive config via `config.Map` in their `Configure` method. See `framework/config/` and existing modules for the pattern.
- **Logging** — use `framework/log.Logger`, not `log` stdlib. Per-delivery loggers via `target.DeliveryLogger(...)`.



================================================
FILE: COPYING
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: Dockerfile
================================================
FROM golang:1.23-alpine AS build-env

ARG ADDITIONAL_BUILD_TAGS=""

RUN set -ex && \
    apk upgrade --no-cache --available && \
    apk add --no-cache build-base

WORKDIR /maddy

COPY go.mod go.sum ./
RUN go mod download

COPY . ./
RUN mkdir -p /pkg/data && \
    cp maddy.conf.docker /pkg/data/maddy.conf && \
    ./build.sh --builddir /tmp --destdir /pkg/ --tags "docker ${ADDITIONAL_BUILD_TAGS}" build install

FROM alpine:3.21.2
LABEL maintainer="fox.cpp@disroot.org"
LABEL org.opencontainers.image.source=https://github.com/foxcpp/maddy

RUN set -ex && \
    apk upgrade --no-cache --available && \
    apk --no-cache add ca-certificates
COPY --from=build-env /pkg/data/maddy.conf /data/maddy.conf
COPY --from=build-env /pkg/usr/local/bin/maddy /bin/

EXPOSE 25 143 993 587 465
VOLUME ["/data"]
ENTRYPOINT ["/bin/maddy", "-config", "/data/maddy.conf"]
CMD ["run"]


================================================
FILE: HACKING.md
================================================
## Design goals

- **Make it easy to deploy.**
  Minimal configuration changes should be required to get a typical mail server
  running. Though, it is important to avoid making guesses for a
  "zero-configuration". A wrong guess is worse than no guess.

- **Provide 80% of needed components.**
  E-mail has evolved into a huge mess. With a single package to do one thing, it
  quickly turns into a maintenance nightmare. Put all stuff mail server
  typically needs into a single package. Though, leave controversial or highly
  opinionated stuff out, don't force people to do things our way
  (see next point).

- **Interoperate with existing software.**
  Implement (de-facto) standard protocols not only for clients but also for
  various server-side helper software (content filters, etc).

- **Be secure but interoperable.**
  Verify DKIM signatures by default, use DMRAC policies by default, etc. This
  makes default setup as secure as possible while maintaining reasonable
  interoperability. Though, users can configure maddy to be stricter.

- **Achieve flexibility through composability.**
  Allow connecting components in arbitrary ways instead of restricting users to
  predefined templates.

- **Use Go concurrency features to the full extent.**
  Do as much I/O as possible in parallel to minimize latencies. It is silly to
  not do so when it is possible.

## Design summary

Here is a summary of how things are organized in maddy in general. It explains
things from the developer perspective and is meant to be used as an
introduction by the new developers/contributors. It is recommended to read
user documentation to understand how things work from the user perspective as
well.

- User documentation: [maddy.conf(5)](docs/man/maddy.5.scd)
- Design rationale: [Comments on design (Wiki)][1]

There are components called "modules". They are represented by objects
implementing the module.Module interface. Each module gets its unique name.
The function used to create a module instance is saved with this name as a key
into the global map called "modules registry".

Whenever module needs another module for some functionality, it references it
using a configuration directive with a matcher that internally calls
`modconfig.ModuleFromNode`. That function looks up the module "constructor" in
the registry, calls it with corresponding arguments, checks whether the
returned module satisfies the needed interfaces and then initializes it.

Alternatively, if configuration uses &-syntax to reference existing
configuration block, `ModuleFromNode` simply looks it up in the global instances
registry. All modules defined the configuration as a separate top-level blocks
are created before main initialization and are placed in the instances registry
where they can be looked up as mentioned before.

Top-level defined module instances are initialized (`Init` method) lazily as
they are required by other modules. 'smtp' and 'imap' modules follow a special
initialization path, so they are always initialized directly.

## Error handling

Familiarize yourself with the `github.com/foxcpp/maddy/framework/exterrors`
package and make sure you have the following for returned errors:
- SMTP status information (smtp\_code, smtp\_enchcode, smtp\_msg fields)
  - SMTP message text should contain a generic description of the error
    condition without any details to prevent accidental disclosure of the
    server configuration details.
- `Temporary() == true` for temporary errors (see `exterrors.WithTemporary`)
- Field that includes the module name

The easiest way to get all of these is to use `exterrors.SMTPError`.
Put the original error into the `Err` field, so it can be inspected using
`errors.Is`, `errors.Unwrap`, etc. Put the module name into `CheckName` or
`TargetName`. Add any additional context information using the `Misc` field.
Note, the SMTP status code overrides the result of `exterrors.IsTemporary()`
for that error object, so set it using `exterrors.SMTPCode` that uses
`IsTemporary` to select between two codes.

If the error you are wrapping contains details in its structure fields (like
`*net.OpError`) - copy these values into `Misc` map, put the underlying error
object (`net.OpError.Err`, for example) into the `Err` field.
Avoid using `Reason` unless you are sure you can provide the error message
better than the `Err.Error()` or `Err` is `nil`.

Do not attempt to add a SMTP status information for every single possible
error. Use `exterrors.WithFields` with basic information for errors you don't
expect. The SMTP client will get the "Internal server error" message and this
is generally the right thing to do on unexpected errors.

### Goroutines and panics

If you start any goroutines - make sure to catch panics to make sure severe
bugs will not bring the whole server down.

## Adding a check

"Check" is a module that inspects the message and flags it as spam or rejects
it altogether based on some condition.

The skeleton for the stateful check module can be found in
`internal/check/skeleton.go`.  Throw it into a file in
`internal/check/check_name` directory and start ~~breaking~~ extending it.

If you don't need any per-message state, you can use `StatelessCheck` wrapper.
See `check/dns` directory for a working example.

Here are some guidelines to make sure your check works well:
- RTFM, docs will tell you about any caveats.
- Don't share any state _between_ messages, your code will be executed in
  parallel.
- Use `github.com/foxcpp/maddy/check.FailAction` to select behavior on check
  failures. See other checks for examples on how to use it.
- You can assume that order of check functions execution is as follows:
  `CheckConnection`, `CheckSender`, `CheckRcpt`, `CheckBody`.

## Adding a modifier

"Modifier" is a module that can modify some parts of the message data.

Note, currently this is not possible to modify the body contents, only header
can be modified.

Structure of the modifier implementation is similar to the structure of check
implementation, check `modify/replace\_addr.go` for a working example.

[1]: https://github.com/foxcpp/maddy/wiki/Dev:-Comments-on-design


================================================
FILE: README.md
================================================
Maddy Mail Server
=====================
> Composable all-in-one mail server.

Maddy Mail Server implements all functionality required to run a e-mail
server. It can send messages via SMTP (works as MTA), accept messages via SMTP
(works as MX) and store messages while providing access to them via IMAP.
In addition to that it implements auxiliary protocols that are mandatory
to keep email reasonably secure (DKIM, SPF, DMARC, DANE, MTA-STS).

It replaces Postfix, Dovecot, OpenDKIM, OpenSPF, OpenDMARC and more with one
daemon with uniform configuration and minimal maintenance cost.

**Note:** IMAP storage is "beta". If you are looking for stable and
feature-packed implementation you may want to use Dovecot instead. maddy still
can handle message delivery business.

[![CI status](https://img.shields.io/github/actions/workflow/status/foxcpp/maddy/cicd.yml?style=flat-square)](https://github.com/foxcpp/maddy/actions/workflows/cicd.yml)
[![Issues tracker](https://img.shields.io/github/issues/foxcpp/maddy?style=flat-square)](https://github.com/foxcpp/maddy)

* [Setup tutorial](https://maddy.email/tutorials/setting-up/)
* [Documentation](https://maddy.email/)

* [IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1)
* [Mailing list](https://lists.sr.ht/~foxcpp/maddy)


================================================
FILE: build.sh
================================================
#!/bin/sh

destdir=/
builddir="$PWD/build"
prefix=/usr/local
version=
static=0
if [ "${GOFLAGS}" = "" ]; then
	GOFLAGS="-trimpath" # set some flags to avoid passing "" to go
fi

print_help() {
	cat >&2 <<EOF
Usage:
	./build.sh [options] {build,install}

Script to build, package or install Maddy Mail Server.

Options:
    -h, --help              guess!
    --builddir              directory to build in (default: $builddir)

Options for ./build.sh build:
    --static                build static self-contained executables (musl-libc recommended)
    --tags <tags>           build tags to use
    --version <version>     version tag to embed into executables (default: auto-detect)

Additional flags for "go build" can be provided using GOFLAGS environment variable.

Options for ./build.sh install:
    --prefix <path>         installation prefix (default: $prefix)
    --destdir <path>        system root (default: $destdir)
EOF
}

while :; do
	case "$1" in
		-h|--help)
		   print_help
		   exit
		   ;;
		--builddir)
		   shift
		   builddir="$1"
		   ;;
		--prefix)
		   shift
		   prefix="$1"
		   ;;
		--destdir)
			shift
			destdir="$1"
			;;
		--version)
			shift
			version="$1"
			;;
		--static)
			static=1
			;;
		--tags)
			shift
			tags="$1"
			;;
		--)
			break
			shift
			;;
		-?*)
			echo "Unknown option: ${1}. See --help." >&2
			exit 2
			;;
		*)
			break
	esac
	shift
done

configdir="${destdir}etc/maddy"

if [ "$version" = "" ]; then
	version=unknown
	if [ -e .version ]; then
		version="$(cat .version)"
	fi
	if [ -e .git ] && command -v git 2>/dev/null >/dev/null; then
		version="${version}+$(git rev-parse --short HEAD)"
	fi
fi

set -e

build_man_pages() {
	set +e
	if ! command -v scdoc >/dev/null 2>/dev/null; then
		echo '-- [!] No scdoc utility found. Skipping man pages building.' >&2
		set -e
		return
	fi
	set -e

	echo '-- Building man pages...' >&2

	mkdir -p "${builddir}/man"
	for f in ./docs/man/*.1.scd; do
		scdoc < "$f" > "${builddir}/man/$(basename "$f" .scd)"
	done
}

build() {
	mkdir -p "${builddir}"
	echo "-- Version: ${version}" >&2
	if [ "$(go env CC)" = "" ]; then
        echo '-- [!] No C compiler available. maddy will be built without SQLite3 support and default configuration will be unusable.' >&2
    fi

	if [ "$static" -eq 1 ]; then
		echo "-- Building main server executable..." >&2
		# This is literally impossible to specify this line of arguments as part of ${GOFLAGS}
		# using only POSIX sh features (and even with Bash extensions I can't figure it out).
		go build -trimpath -buildmode pie -tags "$tags osusergo netgo static_build" \
			-ldflags "-extldflags '-fno-PIC -static' -X \"github.com/foxcpp/maddy.Version=${version}\"" \
			-o "${builddir}/maddy" ${GOFLAGS} ./cmd/maddy
	else
		echo "-- Building main server executable..." >&2
		go build -tags "$tags" -trimpath -ldflags="-X \"github.com/foxcpp/maddy.Version=${version}\"" -o "${builddir}/maddy" ${GOFLAGS} ./cmd/maddy
	fi

	build_man_pages

	echo "-- Copying misc files..." >&2

	mkdir -p "${builddir}/systemd"
	cp dist/systemd/*.service "${builddir}/systemd/"
	cp maddy.conf "${builddir}/maddy.conf"
}

install() {
	echo "-- Installing built files..." >&2

	command install -m 0755 -d "${destdir}/${prefix}/bin/"
	command install -m 0755 "${builddir}/maddy" "${destdir}/${prefix}/bin/"
	command ln -sf maddy "${destdir}/${prefix}/bin/maddyctl"
	command install -m 0755 -d "${configdir}"


	# We do not want to overwrite existing configuration.
	# If the file exists, then save it with .default suffix and warn user.
	if [ ! -e "${configdir}/maddy.conf" ]; then
		command install -m 0644 ./maddy.conf "${configdir}/maddy.conf"
	else
		echo "-- [!] Configuration file ${configdir}/maddy.conf exists, saving to ${configdir}/maddy.conf.default" >&2
		command install -m 0644 ./maddy.conf "${configdir}/maddy.conf.default"
	fi

	# Attempt to install systemd units only for Linux.
	# Check is done using GOOS instead of uname -s to account for possible
	# package cross-compilation.
	# Though go command might be unavailable if build.sh is run
	# with sudo and go installation is user-specific, so fallback
	# to using uname -s in the end.
	set +e
	if command -v go >/dev/null 2>/dev/null; then
		set -e
		if [ "$(go env GOOS)" = "linux" ]; then
			command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/"
			command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/"
		fi
	else
		set -e
		if [ "$(uname -s)" = "Linux" ]; then
			command install -m 0755 -d "${destdir}/${prefix}/lib/systemd/system/"
			command install -m 0644 "${builddir}"/systemd/*.service "${destdir}/${prefix}/lib/systemd/system/"
		fi
	fi

	if [ -e "${builddir}"/man ]; then
		command install -m 0755 -d "${destdir}/${prefix}/share/man/man1/"
		for f in "${builddir}"/man/*.1; do
			command install -m 0644 "$f" "${destdir}/${prefix}/share/man/man1/"
		done
	fi
}

# Old build.sh compatibility
install_pkg() {
	echo "-- [!] Replace 'install_pkg' with 'install' in build.sh invocation" >&2
	install
}
package() {
	echo "-- [!] Replace 'package' with 'build' in build.sh invocation" >&2
	build
}

if [ $# -eq 0 ]; then
	build
else
	for arg in "$@"; do
		eval "$arg"
	done
fi


================================================
FILE: cmd/README.md
================================================
maddy executables
-------------------

### maddy

Main server executable.

### maddy-pam-helper, maddy-shadow-helper

Utilities compatible with the auth.external module that call libpam or read
/etc/shadow on Unix systems.


================================================
FILE: cmd/maddy/main.go
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package main

import (
	_ "github.com/foxcpp/maddy"
	maddycli "github.com/foxcpp/maddy/internal/cli"
	_ "github.com/foxcpp/maddy/internal/cli/ctl"
)

func main() {
	maddycli.Run()
}


================================================
FILE: cmd/maddy-pam-helper/README.md
================================================
## maddy-pam-helper

External setuid binary for interaction with shadow passwords database or other
privileged objects necessary to run PAM authentication.

### Building

It is really easy to build it using any GCC:
```
gcc pam.c main.c -lpam -o maddy-pam-helper
```

Yes, it is not a Go binary.


### Installation

maddy-pam-helper is kinda dangerous binary and should not be allowed to be
executed by everybody but maddy's user. At the same moment it needs to have
access to read-protected files. For this reason installation should be done
very carefully to make sure to not introduce any security "holes".

#### First method

```shell
chown maddy: /usr/bin/maddy-pam-helper
chmod u+x,g-x,o-x /usr/bin/maddy-pam-helper
```

Also maddy-pam-helper needs access to /etc/shadow, one of the ways to provide
it is to set file capability CAP_DAC_READ_SEARCH:
```
setcap cap_dac_read_search+ep /usr/bin/maddy-pam-helper
```

#### Second method

Another, less restrictive is to make it setuid-root (assuming you have both maddy user and group):
```
chown root:maddy /usr/bin/maddy-pam-helper
chmod u+xs,g+x,o-x /usr/bin/maddy-pam-helper
```

#### Third method

The best way actually is to create `shadow` group and grant access to
/etc/shadow to it and then make maddy-pam-helper setgid-shadow:
```
groupadd shadow
chown :shadow /etc/shadow
chmod g+r /etc/shadow
chown maddy:shadow /usr/bin/maddy-pam-helper
chmod u+x,g+xs /usr/bin/maddy-pam-helper
```

Pick what works best for you.

### PAM service

maddy-pam-helper uses custom service instead of pretending to be su or sudo.
Because of this you should configure PAM to accept it.

Minimal example using local passwd/shadow database for authentication can be
found in [maddy.conf][maddy.conf] file.
It should be put into /etc/pam.d/maddy.


================================================
FILE: cmd/maddy-pam-helper/maddy.conf
================================================
#%PAM-1.0
auth	required	pam_unix.so
account	required	pam_unix.so


================================================
FILE: cmd/maddy-pam-helper/main.c
================================================
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <security/pam_appl.h>
#include "pam.h"

/*
I really doubt it is a good idea to bring Go to the binary whose primary task
is to call libpam using CGo anyway.
*/

int run(void) {
    char *username = NULL, *password = NULL;
    size_t username_buf_len = 0, password_buf_len = 0;

    ssize_t username_len = getline(&username, &username_buf_len, stdin);
    if (username_len < 0) {
        perror("getline username");
        return 2;
    }

    ssize_t password_len = getline(&password, &password_buf_len, stdin);
    if (password_len < 0) {
        perror("getline password");
        return 2;
    }

    // Cut trailing \n.
    if (username_len > 0) {
        username[username_len - 1] = 0;
    }
    if (password_len > 0) {
        password[password_len - 1] = 0;
    }

    struct error_obj err = run_pam_auth(username, password);
    if (err.status != 0) {
        if (err.status == 2) {
            fprintf(stderr, "%s: %s\n", err.func_name, err.error_msg);
        }
        return err.status;
    }

    return 0;
}

#ifndef CGO
int main() {
    return run();
}
#endif


================================================
FILE: cmd/maddy-pam-helper/main.go
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package main

/*
#cgo LDFLAGS: -lpam
#cgo CFLAGS: -DCGO -Wall -Wextra -Werror -Wno-unused-parameter -Wno-error=unused-parameter -Wpedantic -std=c99
extern int run();
*/
import "C"
import "os"

/*
Apparently, some people would not want to build it manually by calling GCC.
Here we do it for them. Not going to tell them that resulting file is 800KiB
bigger than one built using only C compiler.
*/

func main() {
	i := int(C.run())
	os.Exit(i)
}


================================================
FILE: cmd/maddy-pam-helper/pam.c
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2022 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <security/pam_appl.h>
#include "pam.h"

static int conv_func(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr) {
    struct pam_response *reply = malloc(sizeof(struct pam_response));
    if (reply == NULL) {
        return PAM_CONV_ERR;
    }

    char* password_cpy = malloc(strlen((char*)appdata_ptr)+1);
    if (password_cpy == NULL) {
        return PAM_CONV_ERR;
    }
    memcpy(password_cpy, (char*)appdata_ptr, strlen((char*)appdata_ptr)+1);

    reply->resp = password_cpy;
    reply->resp_retcode = 0;

    // PAM frees pam_response for us.
    *resp = reply;

    return PAM_SUCCESS;
}

struct error_obj run_pam_auth(const char *username, char *password) {
    const struct pam_conv local_conv = { conv_func, password };
    pam_handle_t *local_auth = NULL;
    int status = pam_start("maddy", username, &local_conv, &local_auth);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_start";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_authenticate(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN) {
            ret_val.status = 1;
        } else {
            ret_val.status = 2;
        }
        ret_val.func_name = "pam_authenticate";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_acct_mgmt(local_auth, PAM_SILENT|PAM_DISALLOW_NULL_AUTHTOK);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        if (status == PAM_AUTH_ERR || status == PAM_USER_UNKNOWN || status == PAM_NEW_AUTHTOK_REQD) {
            ret_val.status = 1;
        } else {
            ret_val.status = 2;
        }
        ret_val.func_name = "pam_acct_mgmt";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    status = pam_end(local_auth, status);
    if (status != PAM_SUCCESS) {
        struct error_obj ret_val;
        ret_val.status = 2;
        ret_val.func_name = "pam_end";
        ret_val.error_msg = pam_strerror(local_auth, status);
        return ret_val;
    }

    struct error_obj ret_val;
    ret_val.status = 0;
    ret_val.func_name = NULL;
    ret_val.error_msg = NULL;
    return ret_val;
}



================================================
FILE: cmd/maddy-pam-helper/pam.h
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#pragma once

struct error_obj {
    int status;
    const char* func_name;
    const char* error_msg;
};

struct error_obj run_pam_auth(const char *username, char *password);


================================================
FILE: cmd/maddy-shadow-helper/README.md
================================================
## maddy-shadow-helper

External helper binary for interaction with shadow passwords database.
Unlike maddy-pam-helper it supports only local shadow database but it does
not have any C dependencies.

### Installation

maddy-shadow-helper is kinda dangerous binary and should not be allowed to be
executed by everybody but maddy's user. At the same moment it needs to have
access to read-protected files. For this reason installation should be done
very carefully to make sure to not introduce any security "holes".

#### First method

```shell
chown maddy: /usr/bin/maddy-shadow-helper
chmod u+x,g-x,o-x /usr/bin/maddy-shadow-helper
```

Also maddy-shadow-helper needs access to /etc/shadow, one of the ways to provide
it is to set file capability CAP_DAC_READ_SEARCH:
```
setcap cap_dac_read_search+ep /usr/bin/maddy-shadow-helper
```

#### Second method

Another, less restrictive is to make it setuid-root (assuming you have both maddy user and group):
```
chown root:maddy /usr/bin/maddy-shadow-helper
chmod u+xs,g+x,o-x /usr/bin/maddy-shadow-helper
```

#### Third method

The best way actually is to create `shadow` group and grant access to
/etc/shadow to it and then make maddy-shadow-helper setgid-shadow:
```
groupadd shadow
chown :shadow /etc/shadow
chmod g+r /etc/shadow
chown maddy:shadow /usr/bin/maddy-shadow-helper
chmod u+x,g+xs /usr/bin/maddy-shadow-helper
```

Pick what works best for you.


================================================
FILE: cmd/maddy-shadow-helper/main.go
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package main

import (
	"bufio"
	"errors"
	"fmt"
	"os"

	"github.com/foxcpp/maddy/internal/auth/shadow"
)

func main() {
	scnr := bufio.NewScanner(os.Stdin)

	if !scnr.Scan() {
		fmt.Fprintln(os.Stderr, scnr.Err())
		os.Exit(2)
	}
	username := scnr.Text()

	if !scnr.Scan() {
		fmt.Fprintln(os.Stderr, scnr.Err())
		os.Exit(2)
	}
	password := scnr.Text()

	ent, err := shadow.Lookup(username)
	if err != nil {
		if errors.Is(err, shadow.ErrNoSuchUser) {
			os.Exit(1)
		}
		fmt.Fprintln(os.Stderr, err)
		os.Exit(2)
	}

	if !ent.IsAccountValid() {
		fmt.Fprintln(os.Stderr, "account is expired")
		os.Exit(1)
	}

	if !ent.IsPasswordValid() {
		fmt.Fprintln(os.Stderr, "password is expired")
		os.Exit(1)
	}

	if err := ent.VerifyPassword(password); err != nil {
		if errors.Is(err, shadow.ErrWrongPassword) {
			os.Exit(1)
		}
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}


================================================
FILE: config.go
================================================
/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package maddy

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"

	"github.com/foxcpp/maddy/framework/config"
	"github.com/foxcpp/maddy/framework/log"
)

/*
Config matchers for module interfaces.
*/

// logOut structure wraps log.Output and preserves
// configuration directive it was constructed from, allowing
// dynamic reinitialization for purposes of log file rotation.
type logOut struct {
	args []string
	log.Output
}

func logOutput(_ *config.Map, node config.Node) (interface{}, error) {
	if len(node.Args) == 0 {
		return nil, config.NodeErr(node, "expected at least 1 argument")
	}
	if len(node.Children) != 0 {
		return nil, config.NodeErr(node, "can't declare block here")
	}

	return LogOutputOption(node.Args)
}

func LogOutputOption(args []string) (log.Output, error) {
	outs := make([]log.Output, 0, len(args))
	for i, arg := range args {
		switch arg {
		case "stderr":
			outs = append(outs, log.WriterOutput(os.Stderr, false))
		case "stderr_ts":
			outs = append(outs, log.WriterOutput(os.Stderr, true))
		case "syslog":
			syslogOut, err := log.SyslogOutput()
			if err != nil {
				return nil, fmt.Errorf("failed to connect to syslog daemon: %v", err)
			}
			outs = append(outs, syslogOut)
		case "off":
			if len(args) != 1 {
				return nil, errors.New("'off' can't be combined with other log targets")
			}
			return log.NopOutput{}, nil
		default:
			// log file paths are converted to absolute to make sure
			// we will be able to recreate them in right location
			// after changing working directory to the state dir.
			absPath, err := filepath.Abs(arg)
			if err != nil {
				return nil, err
			}
			// We change the actual argument, so logOut object will
			// keep the absolute path for reinitialization.
			args[i] = absPath

			w, err := os.OpenFile(absPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
			if err != nil {
				return nil, fmt.Errorf("failed to create log file: %v", err)
			}

			outs = append(outs, log.WriteCloserOutput(w, true))
		}
	}

	if len(outs) == 1 {
		return logOut{args, outs[0]}, nil
	}
	return logOut{args, log.MultiOutput(outs...)}, nil
}

func defaultLogOutput() (interface{}, error) {
	return nil, nil
}

func reinitLogging() {
	out, ok := log.DefaultLogger.Out.(logOut)
	if !ok {
		log.Println("Can't reinitialize logger because it was replaced before, this is a bug")
		return
	}

	newOut, err := LogOutputOption(out.args)
	if err != nil {
		log.Println("Can't reinitialize logger:", err)
		return
	}

	if err := out.Close(); err != nil {
		log.Println("Can't close old logger:", err)
	}

	log.DefaultLogger.Out = newOut
}


================================================
FILE: contrib/README.md
================================================
# Community contributed resources

Disclaimer: Nothing inside subdirectories here is directly supported by Maddy
Mail Server maintainers. Some community members may be able to help you or not.

- Kubernetes helm chart is maintained by @acim.


================================================
FILE: contrib/kubernetes/chart/.helmignore
================================================
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/


================================================
FILE: contrib/kubernetes/chart/Chart.yaml
================================================
apiVersion: v2
name: maddy
description: A Helm chart for Kubernetes

# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application

# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.2.6

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 0.4.0


================================================
FILE: contrib/kubernetes/chart/README.md
================================================
# maddy Helm chart for Kubernetes

![Version: 0.2.5](https://img.shields.io/badge/Version-0.2.5-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.4.1](https://img.shields.io/badge/AppVersion-0.4.1-informational?style=flat-square)

This is just initial effort to run maddy within Kubernetes cluster. We have used Deployment resource which has some downsides
but at least this chart will allow you to install maddy relatively easily on your Kubernetes cluster. We have considered
StatefulSet and DaemonSet but such solutions would require much more configuration and in casae of DaemonSet also a TCP
load balancer in front of the nodes.

## Requirement

In order to run maddy properly, you need to have TLS secret under name maddy present in the cluster. If you have commercial
certificate, you can create it by the following command:

```sh
kubectl create secret tls maddy --cert=fullchain.pem --key=privkey.pem
```

If you use cert-manager, just create the secret under name maddy.

## Replication

Default for this chart is 1 replica of maddy. If you try to increase this, you will probably get an error because of
the busy ports 25, 143, 587, etc. We do not support this feature at the moment, so please use just 1 replica. Like said
at the beginning of this document, multiple replicas would probably require to switch do DaemonSet which would further require
to have TCP load balancer and shared storage between all replicas. This is not supported by this chart, sorry.
This chart is used on one node cluster and then installation is straight forward, like described bellow, but if you have
multiple node cluster, please use taints and tolerations to select the desired node. This chart supports tolerations to
be set.

## Configuration

| Key                        | Type   | Default           | Description |
| -------------------------- | ------ | ----------------- | ----------- |
| affinity                   | object | `{}`              |             |
| fullnameOverride           | string | `""`              |             |
| image.pullPolicy           | string | `"IfNotPresent"`  |             |
| image.repository           | string | `"foxcpp/maddy"`  |             |
| image.tag                  | string | `""`              |             |
| imagePullSecrets           | list   | `[]`              |             |
| nameOverride               | string | `""`              |             |
| nodeSelector               | object | `{}`              |             |
| persistence.accessMode     | string | `"ReadWriteOnce"` |             |
| persistence.annotations    | object | `{}`              |             |
| persistence.enabled        | bool   | `false`           |             |
| persistence.path           | string | `"/data"`         |             |
| persistence.size           | string | `"128Mi"`         |             |
| podAnnotations             | object | `{}`              |             |
| podSecurityContext         | object | `{}`              |             |
| replicaCount               | int    | `1`               |             |
| resources                  | object | `{}`              |             |
| securityContext            | object | `{}`              |             |
| service.type               | string | `"NodePort"`      |             |
| serviceAccount.annotations | object | `{}`              |             |
| serviceAccount.create      | bool   | `true`            |             |
| serviceAccount.name        | string | `""`              |             |
| tolerations                | list   | `[]`              |             |

## Installing the chart

```sh
helm upgrade --install maddy ./chart --set service.externapIPs[0]=1.2.3.4
```

1.2.3.4 is your public IP of the node.

## maddy configuration

Feel free to tweak files/maddy.conf and files/aliases according to your needs.


================================================
FILE: contrib/kubernetes/chart/files/aliases
================================================
info@example.org: foxcpp@example.org


================================================
FILE: contrib/kubernetes/chart/files/maddy.conf
================================================
## maddy 0.3 - default configuration file (2020-05-31)
# Suitable for small-scale deployments. Uses its own format for local users DB,
# should be managed via maddy subcommands.
#
# See tutorials at https://foxcpp.dev/maddy for guidance on typical
# configuration changes.
#
# See manual pages (also available at https://foxcpp.dev/maddy) for reference
# documentation.

# ----------------------------------------------------------------------------
# Base variables

$(hostname) = mx1.example.org
$(primary_domain) = example.org
$(local_domains) = $(primary_domain)

tls file /etc/maddy/certs/fullchain.pem /etc/maddy/certs/privkey.pem

# ----------------------------------------------------------------------------
# Local storage & authentication

# pass_table provides local hashed passwords storage for authentication of
# users. It can be configured to use any "table" module, in default
# configuration a table in SQLite DB is used.
# Table can be replaced to use e.g. a file for passwords. Or pass_table module
# can be replaced altogether to use some external source of credentials (e.g.
# PAM, /etc/shadow file).
#
# If table module supports it (sql_table does) - credentials can be managed
# using 'maddy creds' command.

auth.pass_table local_authdb {
    table sql_table {
        driver sqlite3
        dsn credentials.db
        table_name passwords
    }
}

# imapsql module stores all indexes and metadata necessary for IMAP using a
# relational database. It is used by IMAP endpoint for mailbox access and
# also by SMTP & Submission endpoints for delivery of local messages.
#
# IMAP accounts, mailboxes and all message metadata can be inspected using
# imap-* subcommands of maddy.

storage.imapsql local_mailboxes {
    driver sqlite3
    dsn imapsql.db
}

# ----------------------------------------------------------------------------
# SMTP endpoints + message routing

hostname $(hostname)

msgpipeline local_routing {
    dmarc yes
    check {
        require_matching_ehlo
        require_mx_record
        dkim
        spf
    }

    # Insert handling for special-purpose local domains here.
    # e.g.
    # destination lists.example.org {
    #     deliver_to lmtp tcp://127.0.0.1:8024
    # }

    destination postmaster $(local_domains) {
        modify {
            replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3"
            replace_rcpt file /data/aliases
        }

        deliver_to &local_mailboxes
    }

    default_destination {
        reject 550 5.1.1 "User doesn't exist"
    }
}

smtp tcp://0.0.0.0:25 {
    limits {
        # Up to 20 msgs/sec across max. 10 SMTP connections.
        all rate 20 1s
        all concurrency 10
    }

    source $(local_domains) {
        reject 501 5.1.8 "Use Submission for outgoing SMTP"
    }
    default_source {
        destination postmaster $(local_domains) {
            deliver_to &local_routing
        }
        default_destination {
            reject 550 5.1.1 "User doesn't exist"
        }
    }
}

submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
    limits {
        # Up to 50 msgs/sec across any amount of SMTP connections.
        all rate 50 1s
    }

    auth &local_authdb

    source $(local_domains) {
        destination postmaster $(local_domains) {
            deliver_to &local_routing
        }
        default_destination {
            modify {
                dkim $(primary_domain) $(local_domains) default
            }
            deliver_to &remote_queue
        }
    }
    default_source {
        reject 501 5.1.8 "Non-local sender domain"
    }
}

target.remote outbound_delivery {
    limits {
        # Up to 20 msgs/sec across max. 10 SMTP connections
        # for each recipient domain.
        destination rate 20 1s
        destination concurrency 10
    }
    mx_auth {
        dane
        mtasts {
            cache fs
            fs_dir mtasts_cache/
        }
        local_policy {
            min_tls_level encrypted
            min_mx_level none
        }
    }
}

target.queue remote_queue {
    target &outbound_delivery

    autogenerated_msg_domain $(primary_domain)
    bounce {
        destination postmaster $(local_domains) {
            deliver_to &local_routing
        }
        default_destination {
            reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
        }
    }
}

# ----------------------------------------------------------------------------
# IMAP endpoints

imap tls://0.0.0.0:993 tcp://0.0.0.0:143 {
    auth &local_authdb
    storage &local_mailboxes
}


================================================
FILE: contrib/kubernetes/chart/templates/NOTES.txt
================================================


================================================
FILE: contrib/kubernetes/chart/templates/_helpers.tpl
================================================
{{/*
Expand the name of the chart.
*/}}
{{- define "maddy.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "maddy.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "maddy.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "maddy.labels" -}}
helm.sh/chart: {{ include "maddy.chart" . }}
{{ include "maddy.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "maddy.selectorLabels" -}}
app.kubernetes.io/name: {{ include "maddy.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "maddy.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "maddy.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}


================================================
FILE: contrib/kubernetes/chart/templates/configmap.yaml
================================================
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{include "maddy.fullname" .}}
  labels: {{- include "maddy.labels" . | nindent 4}}
data:
  maddy.conf: |
{{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | indent 4 }}
  aliases: |
{{ tpl (.Files.Get "files/aliases") . | printf "%s" | indent 4 }}


================================================
FILE: contrib/kubernetes/chart/templates/deployment.yaml
================================================
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "maddy.fullname" . }}
  labels:
    {{- include "maddy.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "maddy.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ tpl (.Files.Get "files/maddy.conf") . | printf "%s" | sha256sum }}
        checksum/aliases: {{ tpl (.Files.Get "files/aliases") . | printf "%s" | sha256sum }}
    {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
    {{- end }}
      labels:
        {{- include "maddy.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "maddy.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      initContainers:
        - name: init
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: busybox
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          command:
            - sh
            - -c
            - cp /tmp/maddy/* /data/.
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            - name: data
              mountPath: {{ .Values.persistence.path }}
              {{- if .Values.persistence.subPath }}
              subPath: {{ .Values.persistence.subPath }}
              {{- end }}
            - name: config
              mountPath: /tmp/maddy
      containers:
        - name: {{ .Chart.Name }}
          securityContext:
            {{- toYaml .Values.securityContext | nindent 12 }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: smtp
              containerPort: 25
              protocol: TCP
            - name: imaps
              containerPort: 993
              protocol: TCP
            - name: starttls
              containerPort: 587
              protocol: TCP
          # livenessProbe:
          #   httpGet:
          #     path: /
          #     port: http
          # readinessProbe:
          #   httpGet:
          #     path: /
          #     port: http
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
          volumeMounts:
            - name: data
              mountPath: {{ .Values.persistence.path }}
              {{- if .Values.persistence.subPath }}
              subPath: {{ .Values.persistence.subPath }}
              {{- end }}
            - name: tls
              mountPath: /etc/maddy/certs/fullchain.pem
              subPath: tls.crt
            - name: tls
              mountPath: /etc/maddy/certs/privkey.pem
              subPath: tls.key
      volumes:
        - name: data
          {{- if .Values.persistence.enabled }}
          persistentVolumeClaim:
            claimName: {{ default (include "maddy.fullname" .) .Values.persistence.existingClaim }}
          {{- else }}
          emptyDir: {}
          {{- end }}
        - name: config
          configMap:
            name: {{include "maddy.fullname" .}}
        - name: tls
          secret:
            secretName: {{include "maddy.fullname" .}}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}


================================================
FILE: contrib/kubernetes/chart/templates/pvc.yaml
================================================
{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: {{ include "maddy.fullname" . }}
  annotations:
  {{- with .Values.persistence.annotations  }}
  {{ toYaml . | indent 4 }}
  {{- end }}
  labels:
    {{- include "maddy.labels" . | nindent 4 }}
spec:
  accessModes:
    - {{ .Values.persistence.accessMode }}
  resources:
    requests:
      storage: {{ .Values.persistence.size }}
  {{- if .Values.persistence.storageClass }}
  storageClassName: {{ .Values.persistence.storageClass }}
  {{- end }}
{{- end -}}



================================================
FILE: contrib/kubernetes/chart/templates/service.yaml
================================================
apiVersion: v1
kind: Service
metadata:
  name: {{ include "maddy.fullname" . }}
  labels:
    {{- include "maddy.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: 25
      targetPort: smtp
      protocol: TCP
      name: smtp
    - port: 993
      targetPort: imaps
      protocol: TCP
      name: imaps
    - port: 587
      targetPort: starttls
      protocol: TCP
      name: starttls
  selector:
    {{- include "maddy.selectorLabels" . | nindent 4 }}
  {{- with .Values.service.externalIPs }}
  externalIPs:
  {{- toYaml . | nindent 6 }}
  {{- end -}}


================================================
FILE: contrib/kubernetes/chart/templates/serviceaccount.yaml
================================================
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "maddy.serviceAccountName" . }}
  labels:
    {{- include "maddy.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
{{- end }}


================================================
FILE: contrib/kubernetes/chart/templates/tests/test-connection.yaml
================================================
apiVersion: v1
kind: Pod
metadata:
  name: "{{ include "maddy.fullname" . }}-test-connection"
  labels:
    {{- include "maddy.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": test-success
spec:
  containers:
    - name: wget
      image: busybox
      command: ['wget']
      args: ['{{ include "maddy.fullname" . }}:{{ .Values.service.port }}']
  restartPolicy: Never


================================================
FILE: contrib/kubernetes/chart/values.yaml
================================================
# Default values for maddy.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1 # Multiple replicas are not supported, please don't change this.

image:
  repository: foxcpp/maddy
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: ""

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  # Specifies whether a service account should be created
  create: true
  # Annotations to add to the service account
  annotations: {}
  # The name of the service account to use.
  # If not set and create is true, a name is generated using the fullname template
  name: ""

podAnnotations: {}

podSecurityContext:
  {}
  # fsGroup: 2000

securityContext:
  {}
  # capabilities:
  #   drop:
  #   - ALL
  # readOnlyRootFilesystem: true
  # runAsNonRoot: true
  # runAsUser: 1000

# Set externalPIs to your public IP(s) of the node running maddy. In case of multiple nodes, you need to set tolerations
# and taints in order to run maddy on the exact node.
service:
  type: NodePort
  # externalIPs:

resources:
  {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
  # limits:
  #   cpu: 100m
  #   memory: 128Mi
  # requests:
  #   cpu: 100m
  #   memory: 128Mi

persistence:
  enabled: false
  # existingClaim: ""
  accessMode: ReadWriteOnce
  size: 128Mi
  # storageClass: ""
  path: /data
  annotations: {}
  # subPath: "" # only mount a subpath of the Volume into the pod

nodeSelector: {}

tolerations: []

affinity: {}


================================================
FILE: directories.go
================================================
//go:build !docker
// +build !docker

package maddy

var (
	// ConfigDirectory specifies platform-specific value
	// that should be used as a location of default configuration
	//
	// It should not be changed and is defined as a variable
	// only for purposes of modification using -X linker flag.
	ConfigDirectory = "/etc/maddy"

	// DefaultStateDirectory specifies platform-specific
	// default for StateDirectory.
	//
	// Most code should use StateDirectory instead since
	// it will contain the effective location of the state
	// directory.
	//
	// It should not be changed and is defined as a variable
	// only for purposes of modification using -X linker flag.
	DefaultStateDirectory = "/var/lib/maddy"

	// DefaultRuntimeDirectory specifies platform-specific
	// default for RuntimeDirectory.
	//
	// Most code should use RuntimeDirectory instead since
	// it will contain the effective location of the state
	// directory.
	//
	// It should not be changed and is defined as a variable
	// only for purposes of modification using -X linker flag.
	DefaultRuntimeDirectory = "/run/maddy"

	// DefaultLibexecDirectory specifies platform-specific
	// default for LibexecDirectory.
	//
	// Most code should use LibexecDirectory since it will
	// contain the effective location of the libexec
	// directory.
	//
	// It should not be changed and is defined as a variable
	// only for purposes of modification using -X linker flag.
	DefaultLibexecDirectory = "/usr/lib/maddy"
)


================================================
FILE: directories_docker.go
================================================
//go:build docker
// +build docker

package maddy

var (
	ConfigDirectory         = "/data"
	DefaultStateDirectory   = "/data"
	DefaultRuntimeDirectory = "/tmp"
	DefaultLibexecDirectory = "/usr/lib/maddy"
)


================================================
FILE: dist/README.md
================================================
Distribution files for maddy
------------------------------

**Disclaimer:** Most of the files here are maintained in a "best-effort" way.
That is, they may break or become outdated from time to time. Caveat emptor.

## integration + scripts

These directories provide pre-made configuration snippets suitable for
easy integration with external software.

Usually, this is what you use when you put `import integration/something` in
your config.

## systemd unit

`maddy.service` launches using default config path (/etc/maddy/maddy.conf).
`maddy@.service` launches maddy using custom config path. E.g.
`maddy@foo.service` will use /etc/maddy/foo.conf.

Additionally, unit files apply strict sandboxing, limiting maddy permissions on
the system to a bare minimum. Subset of these options makes it impossible for
privileged authentication helper binaries to gain required permissions, so you
may have to disable it when using system account-based authentication with
maddy running as a unprivileged user.

## fail2ban configuration

Configuration files for use with fail2ban. Assume either `backend = systemd` specified
in system-wide configuration or log file written to /var/log/maddy/maddy.log.

See https://github.com/foxcpp/maddy/wiki/fail2ban-configuration for details.

## logrotate configuration

Meant for logs rotation when logging to file is used.

## vim ftdetect/ftplugin/syntax files

Minimal supplement to make configuration files more readable and help you see
typos in directive names.


================================================
FILE: dist/apparmor/dev.foxcpp.maddy
================================================
# AppArmor profile for maddy daemon.
# vim:syntax=apparmor:ts=2:sw=2:et

#include <tunables/global>

profile dev.foxcpp.maddy /usr{/local,}/bin/maddy {
  #include <abstractions/base>
  #include <abstractions/ssl_certs>
  #include <abstractions/ssl_keys>
  /etc/ca-certificates/** r,

  /etc/resolv.conf r,
  /proc/sys/net/core/somaxconn r,
  /sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
  deny ptrace,
  capability net_bind_service,
  network tcp,
  network unix,

  # systemd process management and Type=notify
  signal (receive) peer=unconfined,
  signal (receive) peer=/usr/bin/systemd,
  unix (create, connect, send, setopt) type=dgram addr=@*,
  /run/systemd/notify w,

  /etc/maddy/** r,
  owner /run/maddy/ rw,
  owner /run/maddy/** rwkl,
  owner /var/lib/maddy/ rw,
  owner /var/lib/maddy/** rwk,
  owner /var/lib/maddy/**.db-{wal,shm} rmk,

  /usr{/local,}/lib/maddy/* PUx,

  /usr{/local,}/bin/maddy{,ctl} rmix,

  #include if exists <local/dev.foxcpp.maddy>
}


================================================
FILE: dist/fail2ban/filter.d/maddy-auth.conf
================================================
[INCLUDES]
before = common.conf

[Definition]
failregex    = authentication failed\t\{\"reason\":\".*\",\"src_ip\"\:\"<HOST>:\d+\"\,\"username\"\:\".*\"\}$
journalmatch = _SYSTEMD_UNIT=maddy.service + _COMM=maddy


================================================
FILE: dist/fail2ban/filter.d/maddy-dictonary-attack.conf
================================================
[INCLUDES]
before = common.conf

[Definition]
failregex    = smtp\: MAIL FROM error repeated a lot\, possible dictonary attack\t\{\"count\"\:\d+,\"msg_id\":\".+\",\"src_ip\"\:\"<HOST>:\d+\"\}$
               smtp\: too many RCPT errors\, possible dictonary attack\t\{\"msg_id\":\".+\","src_ip":"<HOST>:\d+\"\}
journalmatch = _SYSTEMD_UNIT=maddy.service + _COMM=maddy


================================================
FILE: dist/fail2ban/jail.d/maddy-auth.conf
================================================
[maddy-auth]
port     = 993,465,25
filter   = maddy-auth
bantime  = 96h
backend  = systemd


================================================
FILE: dist/fail2ban/jail.d/maddy-dictonary-attack.conf
================================================
[maddy-dictonary-attack]
port     = 993,465,25
filter   = maddy-dictonary-attack
bantime  = 72h
maxretry = 3
findtime = 6h
backend  = systemd


================================================
FILE: dist/install.sh
================================================
#!/bin/bash

DESTDIR=$DESTDIR
if [ -z "$PREFIX" ]; then
    PREFIX=/usr/local
fi
if [ -z "$FAIL2BANDIR" ]; then
    FAIL2BANDIR=/etc/fail2ban
fi
if [ -z "$CONFDIR" ]; then
    CONFDIR=/etc/maddy
fi

script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $script_dir

install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/ftdetect/" vim/ftdetect/maddy-conf.vim
install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/ftplugin/" vim/ftplugin/maddy-conf.vim
install -Dm 0644 -t "$DESTDIR/$PREFIX/share/vim/vimfiles/syntax/" vim/syntax/maddy-conf.vim

install -Dm 0644 -t "$DESTDIR/$FAIL2BANDIR/jail.d/" fail2ban/jail.d/*
install -Dm 0644 -t "$DESTDIR/$FAIL2BANDIR/filter.d/" fail2ban/filter.d/*

install -Dm 0644 -t "$DESTDIR/$PREFIX/lib/systemd/system/" systemd/maddy.service systemd/maddy@.service


================================================
FILE: dist/logrotate.d/maddy
================================================
/var/log/maddy/maddy.log {
    missingok
    su maddy maddy
    postrotate
        /usr/bin/killall -USR1 maddy
    endscript
}


================================================
FILE: dist/systemd/maddy.service
================================================
[Unit]
Description=maddy mail server
Documentation=man:maddy(1)
Documentation=man:maddy.conf(5)
Documentation=https://maddy.email
After=network-online.target

[Service]
Type=notify
NotifyAccess=main

User=maddy
Group=maddy

# cd to state directory to make sure any relative paths
# in config will be relative to it unless handled specially.
WorkingDirectory=/var/lib/maddy

ConfigurationDirectory=maddy
RuntimeDirectory=maddy
StateDirectory=maddy
LogsDirectory=maddy
ReadOnlyPaths=/usr/lib/maddy
ReadWritePaths=/var/lib/maddy

# Strict sandboxing. You have no reason to trust code written by strangers from GitHub.
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectHostname=true
ProtectClock=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

# Additional sandboxing. You need to disable all of these options
# for privileged helper binaries (for system auth) to work correctly.
NoNewPrivileges=true
PrivateDevices=true
DeviceAllow=/dev/syslog
RestrictSUIDSGID=true
ProtectKernelModules=true
MemoryDenyWriteExecute=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true

# Graceful shutdown with a reasonable timeout.
TimeoutStopSec=7s
KillMode=mixed
KillSignal=SIGTERM

# Required to bind on ports lower than 1024.
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# Force all files created by maddy to be only readable by it
# and maddy group.
UMask=0007

# Bump FD limitations. Even idle mail server can have a lot of FDs open (think
# of idle IMAP connections, especially ones abandoned on the other end and
# slowly timing out).
LimitNOFILE=131072

# Limit processes count to something reasonable to
# prevent resources exhausting due to big amounts of helper
# processes launched.
LimitNPROC=512

# Restart server on any problem.
Restart=on-failure
# ... Unless it is a configuration problem.
RestartPreventExitStatus=2

ExecStart=/usr/local/bin/maddy run

ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target


================================================
FILE: dist/systemd/maddy@.service
================================================
[Unit]
Description=maddy mail server (using %i.conf)
Documentation=man:maddy(1)
Documentation=man:maddy.conf(5)
Documentation=https://maddy.email
After=network-online.target

[Service]
Type=notify
NotifyAccess=main

User=maddy
Group=maddy

ConfigurationDirectory=maddy
RuntimeDirectory=maddy
StateDirectory=maddy
LogsDirectory=maddy
ReadOnlyPaths=/usr/lib/maddy
ReadWritePaths=/var/lib/maddy

# Strict sandboxing. You have no reason to trust code written by strangers from GitHub.
PrivateTmp=true
PrivateHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectHostname=true
ProtectClock=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
DeviceAllow=/dev/syslog

# Additional sandboxing. You need to disable all of these options
# for privileged helper binaries (for system auth) to work correctly.
NoNewPrivileges=true
PrivateDevices=true
RestrictSUIDSGID=true
ProtectKernelModules=true
MemoryDenyWriteExecute=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true

# Graceful shutdown with a reasonable timeout.
TimeoutStopSec=7s
KillMode=mixed
KillSignal=SIGTERM

# Required to bind on ports lower than 1024.
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# Force all files created by maddy to be only readable by it and
# maddy group.
UMask=0007

# Bump FD limitations. Even idle mail server can have a lot of FDs open (think
# of idle IMAP connections, especially ones abandoned on the other end and
# slowly timing out).
LimitNOFILE=131072

# Limit processes count to something reasonable to
# prevent resources exhausting due to big amounts of helper
# processes launched.
LimitNPROC=512

# Restart server on any problem.
Restart=on-failure
# ... Unless it is a configuration problem.
RestartPreventExitStatus=2

ExecStart=/usr/local/bin/maddy --config /etc/maddy/%i.conf run

ExecReload=/bin/kill -USR2 $MAINPID

[Install]
WantedBy=multi-user.target


================================================
FILE: dist/vim/ftdetect/maddy-conf.vim
================================================
au BufNewFile,BufRead /etc/maddy/*,maddy.conf setf maddy-conf


================================================
FILE: dist/vim/ftplugin/maddy-conf.vim
================================================
setlocal commentstring=#\ %s

" That is convention for maddy configs. Period.
"    - fox.cpp (maddy developer)
setlocal expandtab
setlocal tabstop=4
setlocal softtabstop=4
setlocal shiftwidth=4


================================================
FILE: dist/vim/syntax/maddy-conf.vim
================================================
" vim: noexpandtab ts=4 sw=4

if exists("b:current_syntax")
	finish
endif

" Lexer-defined rules
syn match		maddyComment	"#.*"
syn region      maddyString		start=+"+ skip=+\\\\\|\\"+ end=+"+ oneline

syn region		maddyBlock		start="{" end="}" transparent fold

hi def link maddyComment	Comment
hi def link maddyString		String

" Parser-defined rules
syn match		maddyMacroName	"[a-z0-9_]" contained containedin=maddyMacro
syn match		maddyMacro      "$(.\{-})" contains=maddyMacroName

syn match		maddyMacroDefSign "=" contained
syn match		maddyMacroDef     "\^$([a-z0-9_]\{-})\s=\s.\+" contains=maddyMacro,maddyMacroDefSign

hi def link maddyMacroName			Identifier
hi def link maddyMacro				Special
hi def link maddyMacroDefSign		Special

" config.Map values
syn keyword maddyBool yes no

syn match maddyInt '\<\d\+\>'
syn match maddyInt '\<[-+]\d\+\>'
syn match maddyFloat '\<[-+]\d\+\.\d*\<'

syn match maddyReference /[ \t]&[^ \t]\+/ms=s+1 contains=maddyReferenceSign
syn match maddyReferenceSign /&/ contained

hi def link maddyBool		Boolean
hi def link maddyInt		Number
hi def link maddyFloat		Float

hi def link maddyReferenceSign	Special

" Module values

" grep --no-file -E 'Register.*\(".+", ' **.go | sed -E 's/.+Register.*\("([^"]+)", .+/\1/' | sort -u
syn keyword maddyModule
	\ checks
	\ command
	\ dane
	\ dkim
	\ dnsbl
	\ dnssec
	\ dummy
	\ extauth
	\ external
	\ file
	\ identity
	\ imap
	\ imap_filters
	\ imapsql
	\ limits
	\ lmtp
	\ loader
	\ local_policy
	\ milter
	\ modifiers
	\ msgpipeline
	\ mtasts
	\ mx_auth
	\ pam
	\ pass_table
	\ plain_separate
	\ queue
	\ regexp
	\ remote
	\ replace_rcpt
	\ replace_sender
	\ require_matching_rdns
	\ require_mx_record
	\ require_tls
	\ rspamd
	\ shadow
	\ smtp
	\ sql_query
	\ sql_table
	\ static
	\ submission

syn keyword maddyDispatchDir
	\ check
	\ modify
	\ default_source
	\ source
	\ default_destination
	\ destination
	\ reject
	\ deliver_to
	\ reroute
	\ dmarc

" grep --no-file -E 'cfg..+\(".+", ' **.go | sed -E 's/.+cfg..+\("([^"]+)", .+/\1/' | sort -u
syn keyword maddyModDir
	\ add
	\ add_header_action
	\ allow_multiple_from
	\ api_path
	\ appendlimit
	\ attempt_starttls
	\ auth
	\ autogenerated_msg_domain
	\ body_canon
	\ bounce
	\ broken_sig_action
	\ buffer
	\ cache
	\ case_insensitive
	\ certs
	\ check_early
	\ client_ipv4
	\ client_ipv6
	\ compression
	\ conn_max_idle_count
	\ conn_max_idle_time
	\ conn_reuse_limit
	\ debug
	\ defer_sender_reject
	\ del
	\ domains
	\ driver
	\ dsn
	\ ehlo
	\ endpoint
	\ enforce_early
	\ enforce_testing
	\ entry
	\ error_resp_action
	\ expand_replaceholders
	\ fail_action
	\ fail_open
	\ file
	\ flags
	\ force_ipv4
	\ fs_dir
	\ fsstore
	\ full_match
	\ hash
	\ header_canon
	\ helper
	\ hostname
	\ imap_filter
	\ init
	\ insecure_auth
	\ io_debug
	\ io_error_action
	\ io_errors
	\ junk_mailbox
	\ key_column
	\ key_path
	\ keys
	\ limits
	\ list
	\ local_ip
	\ location
	\ lookup
	\ mailfrom
	\ max_logged_rcpt_errors
	\ max_message_size
	\ max_parallelism
	\ max_received
	\ max_recipients
	\ max_tries
	\ min_mx_level
	\ min_tls_level
	\ mx_auth
	\ neutral_action
	\ newkey_algo
	\ none_action
	\ no_sig_action
	\ oversign_fields
	\ pass
	\ perdomain
	\ permerr_action
	\ quarantine_threshold
	\ read_timeout
	\ reject_threshold
	\ reject_action
	\ relaxed_requiretls
	\ required_fields
	\ require_sender_match
	\ require_tls
	\ requiretls_override
	\ responses
	\ rewrite_subj_action
	\ run_on
	\ score
	\ selector
	\ set
	\ settings_id
	\ sig_expiry
	\ sign_fields
	\ sign_subdomains
	\ soft_reject_action
	\ softfail_action
	\ SOME_action
	\ source
	\ sqlite3_busy_timeout
	\ sqlite3_cache_size
	\ sqlite3_exclusive_lock
	\ storage
	\ table
	\ table_name
	\ tag
	\ target
	\ targets
	\ temperr_action
	\ tls
	\ tls_client
	\ use_helper
	\ user
	\ value_column
	\ write_timeout

hi def link maddyModDir		Identifier
hi def link maddyModule		Identifier
hi def link maddyDispatchDir		Identifier

let b:current_syntax = "maddy"


================================================
FILE: docs/docker.md
================================================
# Docker

Official Docker image is available from Docker Hub.

It expects configuration file to be available at /data/maddy.conf.

If /data is a Docker volume, then default configuration will be placed there
automatically. If it is used, then MADDY_HOSTNAME, MADDY_DOMAIN environment
variables control the host name and primary domain for the server. TLS
certificate should be placed in /data/tls/fullchain.pem, private key in
/data/tls/privkey.pem

DKIM keys are generated in /data/dkim_keys directory.

## Image tags

- `latest` - A latest stable release. May contain breaking changes.
- `X.Y` - A specific feature branch, it is recommended to use these tags to
  receive bugfixes without the risk of feature-related regressions or breaking
  changes.
- `X.Y.Z` - A specific stable release

## Ports

All standard ports, as described in maddy docs.

- `25` - SMTP inbound port.
- `465`, `587` - SMTP Submission ports
- `993`, `143` - IMAP4 ports

## Volumes

`/data` - maddy state directory. Databases, queues, etc are stored here. You
might want to mount a named volume there. The main configuration file is stored
here too (`/data/maddy.conf`).

## Management utility

To run management commands, create a temporary container with the same
/data directory and put the command after the image name, like this:

```
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.7 creds create foxcpp@maddy.test
docker run --rm -it -v maddydata:/data foxcpp/maddy:0.7 imap-acct create foxcpp@maddy.test
```

Use the same image version as the running server. Things may break badly
otherwise.

Note that, if you modify messages using maddy subcommands while the server is running -
you must ensure that  /tmp from the server is accessible for the management
command. One way to it is to run it using `docker exec` instead of `docker run`:
```
docker exec -it container_name_here maddy creds create foxcpp@maddy.test
```

## Build Tags

Some Maddy features (such as automatic certificate management via ACME with [a non-default libdns provider](../reference/tls-acme/#dns-providers)) require build tags to be passed to Maddy's `build.sh`, as this is run in the Dockerfile you must compile your own Docker image. Build tags can be set via the docker build argument `ADDITIONAL_BUILD_TAGS` e.g. `docker build --build-arg ADDITIONAL_BUILD_TAGS="libdns_acmedns libdns_route53" -t yourorgname/maddy:yourtagname .`.


## TL;DR

```
docker volume create maddydata
docker run \
  --name maddy \
  -e MADDY_HOSTNAME=mx.maddy.test \
  -e MADDY_DOMAIN=maddy.test \
  -v maddydata:/data \
  -p 25:25 \
  -p 143:143 \
  -p 465:465 \
  -p 587:587 \
  -p 993:993 \
  foxcpp/maddy:0.7
```

It will fail on first startup. Copy TLS certificate to /data/tls/fullchain.pem
and key to /data/tls/privkey.pem. Run the server again. Finish DNS configuration
(DKIM keys, etc) as described in [tutorials/setting-up/](../tutorials/setting-up/).


================================================
FILE: docs/faq.md
================================================
# Frequently Asked Questions

## I configured maddy as recommended and gmail still puts my messages in spam

Unfortunately, GMail policies are opaque so we cannot tell why this happens.

Verify that you have a rDNS record set for the IP used
by sender server. Also some IPs may just happen to
have bad reputation - check it with various DNSBLs. In this
case you do not have much of a choice but to replace it.

Additionally, you may try marking multiple messages sent from
your domain as "not spam" in GMail UI.

## Message sending fails with `dial tcp X.X.X.X:25: connect: connection timed out` in log

Your provider is blocking outbound SMTP traffic on port 25.

You either have to ask them to unblock it or forward
all outbound messages via a "smart-host".

## What is resource usage of maddy?

For a small personal server, you do not need much more than a
single 1 GiB of RAM and disk space.

## How to setup a catchall address?

https://github.com/foxcpp/maddy/issues/243#issuecomment-655694512

## maddy command prints a "permission denied" error

Run maddy command under the same user as maddy itself.
E.g.
```
sudo -u maddy maddy creds ...
```

## How maddy compares to MailCow or Mail-In-The-Box?

MailCow and MIAB are bundles of well-known email-related software configured to
work together. maddy is a single piece of software implementing subset of what
MailCow and MIAB offer.

maddy offers more uniform configuration system, more lightweight implementation
and has no dependency on Docker or similar technologies for deployment.

maddy may have more bugs than 20 years old battle-tested software.

It is easier to get help with MailCow/MITB since underlying implementations
are well-understood and have active community.

maddy has no Web interface for administration, that is currently done via CLI
utility.

## How maddy IMAP server compares to WildDuck?

Both are "more secure by definition": root access is not required,
implementation is in memory-safe language, etc.

Both support message compression.

Both have first-class Unicode/internationalization support.

WildDuck may offer easier scalability options. maddy does not require you to
setup MongoDB and Redis servers, though. In fact, maddy in its default
configuration has no dependencies besides libc.

maddy has less builtin authentication providers. This means no
app-specific passwords and all that WildDuck lists under point 4 on their
features page.

maddy currently has no admin Web interface, all necessary DB changes are
performed via CLI utility.

## How maddy SMTP server compares to ZoneMTA?

maddy SMTP server has a lot of similarities to ZoneMTA.
Both have powerful mechanisms for message routing (although designed
differently).

maddy does not require MongoDB server for deployment.

maddy has no web interface for queue inspection. However, it can
easily inspected by looking at files in /var/lib/maddy.

ZoneMTA has a number of features that may make it easier to integrate
with HTTP-based services. maddy speaks standard email protocols (SMTP,
Submission).

## Is there a webmail?

No, at least currently.

I suggest you to check out [alps](https://git.sr.ht/~migadu/alps) if you
are fine with alpha-quality but extremely easy to deploy webmail.

## Is there a content filter (spam filter)?

No. maddy moves email messages around, it does not classify
them as bad or good with the notable exception of sender policies.

It is possible to integrate rspamd using 'rspamd' module. Just add
`rspamd` line to `checks` in `local_routing`, it should just work
in most cases.

## Is it production-ready?

maddy is considered "beta" quality. Several people use it for personal email.

## Single process makes it unreliable. This is dumb!

This is a compromise between ease of management and reliability. Several
measures are implemented in code base in attempt to reduce possible effect
of bugs in one component.

Besides, you are not required to use a single process, it is easy to launch
maddy with a non-default configuration path and connect multiple instances
together using off-the-shelf protocols.


================================================
FILE: docs/index.md
================================================
> Composable all-in-one mail server.

Maddy Mail Server implements all functionality required to run a e-mail
server. It can send messages via SMTP (works as MTA), accept messages via SMTP
(works as MX) and store messages while providing access to them via IMAP.
In addition to that it implements auxiliary protocols that are mandatory
to keep email reasonably secure (DKIM, SPF, DMARC, DANE, MTA-STS).

It replaces Postfix, Dovecot, OpenDKIM, OpenSPF, OpenDMARC and more with one
daemon with uniform configuration and minimal maintenance cost.

**Note:** IMAP storage is "beta". If you are looking for stable and
feature-packed implementation you may want to use Dovecot instead. maddy still
can handle message delivery business.

[![CI status](https://img.shields.io/github/actions/workflow/status/foxcpp/maddy/cicd.yml?style=flat-square)](https://github.com/foxcpp/maddy/actions/workflows/cicd.yml)
[![Issues tracker](https://img.shields.io/github/issues/foxcpp/maddy?style=flat-square)](https://github.com/foxcpp/maddy)

* [Setup tutorial](https://maddy.email/tutorials/setting-up/)
* [Documentation](https://maddy.email/)

* [IRC channel](https://webchat.oftc.net/?channels=maddy&uio=MT11bmRlZmluZWQb1)
* [Mailing list](https://lists.sr.ht/~foxcpp/maddy)


================================================
FILE: docs/internals/quirks.md
================================================
# Implementation quirks

This page documents unusual behavior of the maddy protocols implementations.
Some of these problems break standards, some don't but still can hurt
interoperability.

## SMTP

- `for` field is never included in the `Received` header field.

  This is allowed by [RFC 2821].

## IMAP

### `sql`

- `\Recent` flag is not reset in all cases.

  This _does not_ break [RFC 3501]. Clients relying on it will work (much) less
  efficiently.

[RFC 2821]: https://tools.ietf.org/html/rfc2821
[RFC 3501]: https://tools.ietf.org/html/rfc3501


================================================
FILE: docs/internals/specifications.md
================================================
# Followed specifications

This page lists Internet Standards and other specifications followed by
maddy along with any known deviations.


## Message format

- [RFC 2822] - Internet Message Format
- [RFC 2045] - Multipurpose Internet Mail Extensions (MIME) (part 1)
- [RFC 2046] - Multipurpose Internet Mail Extensions (MIME) (part 2)
- [RFC 2047] - Multipurpose Internet Mail Extensions (MIME) (part 3)
- [RFC 2048] - Multipurpose Internet Mail Extensions (MIME) (part 4)
- [RFC 2049] - Multipurpose Internet Mail Extensions (MIME) (part 5)
- [RFC 6532] - Internationalized Email Headers

- [RFC 2183] - Communicating Presentation Information in Internet Messages: The
  Content-Disposition Header Field

## IMAP

- [RFC 3501] - Internet Message Access Protocol - Version 4rev1
    * **Partial**: `\Recent` flag is not reset sometimes.
- [RFC 2152] - UTF-7

### Extensions

- [RFC 2595] - Using TLS with IMAP, POP3 and ACAP
- [RFC 7889] - The IMAP APPENDLIMIT Extension
- [RFC 3348] - The Internet Message Action Protocol (IMAP4). Child Mailbox
  Extension
- [RFC 6851] - Internet Message Access Protocol (IMAP) - MOVE Extension
- [RFC 6154] - IMAP LIST Extension for Special-Use Mailboxes
    * **Partial**: Only SPECIAL-USE capability.
- [RFC 5255] - Internet Message Access Protocol Internationalization
    * **Partial**: Only I18NLEVEL=1 capability.
- [RFC 4978] - The IMAP COMPRESS Extension
- [RFC 3691] - Internet Message Access Protocol (IMAP) UNSELECT command
- [RFC 2177] - IMAP4 IDLE command
- [RFC 7888] - IMAP4 Non-Synchronizing Literals
    * LITERAL+ capability.
- [RFC 4959] - IMAP Extension for Simple Authentication and Security Layer
  (SASL) Initial Client Response

## SMTP

- [RFC 2033] - Local Mail Transfer Protocol
- [RFC 5321] - Simple Mail Transfer Protocol
- [RFC 6409] - Message Submission for Mail

### Extensions

- [RFC 1870] - SMTP Service Extension for Message Size Declaration
- [RFC 2920] - SMTP Service Extension for Command Pipelining
    * Server support only, not used by SMTP client
- [RFC 2034] - SMTP Service Extension for Returning Enhanced Error Codes
- [RFC 3207] - SMTP Service Extension for Secure SMTP over Transport Layer
  Security
- [RFC 4954] - SMTP Service Extension for Authentication
- [RFC 6152] - SMTP Extension for 8-bit MIME
- [RFC 6531] - SMTP Extension for Internationalized Email

### Misc

- [RFC 6522] - The Multipart/Report Content Type for the Reporting of Mail
  System Administrative Messages
- [RFC 3464] - An Extensible Message Format for Delivery Status Notifications
- [RFC 6533] - Internationalized Delivery Status and Disposition Notifications

## Email security

### User authentication

- [RFC 4422] - Simple Authentication and Security Layer (SASL)
- [RFC 4616] - The PLAIN Simple Authentication and Security Layer (SASL)
  Mechanism

### Sender authentication

- [RFC 6376] - DomainKeys Identified Mail (DKIM) Signatures
- [RFC 7001] - Message Header Field for Indicating Message Authentication Status
- [RFC 7208] - Sender Policy Framework (SPF) for Authorizing Use of Domains in
  Email, Version 1
- [RFC 7372] - Email Authentication Status Codes
- [RFC 7479] - Domain-based Message Authentication, Reporting, and Conformance
  (DMARC)
    * **Partial**: No report generation.
- [RFC 8301] - Cryptographic Algorithm and Key Usage Update to DomainKeys
  Identified Mail (DKIM)
- [RFC 8463] - A New Cryptographic Signature Method for DomainKeys Identified
  Mail (DKIM)
- [RFC 8616] - Email Authentication for Internationalized Mail

### Recipient authentication

- [RFC 4033] - DNS Security Introduction and Requirements
- [RFC 6698] - The DNS-Based Authentication of Named Entities (DANE) Transport
  Layer Security (TLS) Protocol: TLSA
- [RFC 7672] - SMTP Security via Opportunistic DNS-Based Authentication of
  Named Entities (DANE) Transport Layer Security (TLS)
- [RFC 8461] - SMTP MTA Strict Transport Security (MTA-STS)

## Unicode, encodings, internationalization

- [RFC 3492] - Punycode: A Bootstring encoding of Unicode for Internationalized
  Domain Names in Applications (IDNA)
- [RFC 3629] - UTF-8, a transformation format of ISO 10646
- [RFC 5890] - Internationalized Domain Names for Applications (IDNA):
  Definitions and Document Framework
- [RFC 5891] - Internationalized Domain Names for Applications (IDNA): Protocol
- [RFC 7616] - Preparation, Enforcement, and Comparison of Internationalized
  Strings Representing Usernames and Passwords
- [RFC 8264] - PRECIS Framework: Preparation, Enforcement, and Comparison of
  Internationalized Strings in Application Protocols
- [Unicode 11.0.0]
    - [UAX #15] - Unicode Normalization Forms

There is a huge list of non-Unicode encodings supported by message parser used
for IMAP static cache and search.  See [Unicode support](unicode.md) page for
details.

## Misc

- [RFC 5782] - DNS Blacklists and Whitelists


[GH 188]: https://github.com/foxcpp/maddy/issues/188

[RFC 2822]: https://tools.ietf.org/html/rfc2822
[RFC 2045]: https://tools.ietf.org/html/rfc2045
[RFC 2046]: https://tools.ietf.org/html/rfc2046
[RFC 2047]: https://tools.ietf.org/html/rfc2047
[RFC 2048]: https://tools.ietf.org/html/rfc2048
[RFC 2049]: https://tools.ietf.org/html/rfc2049
[RFC 6532]: https://tools.ietf.org/html/rfc6532
[RFC 2183]: https://tools.ietf.org/html/rfc2183
[RFC 3501]: https://tools.ietf.org/html/rfc3501
[RFC 2152]: https://tools.ietf.org/html/rfc2152
[RFC 2595]: https://tools.ietf.org/html/rfc2595
[RFC 7889]: https://tools.ietf.org/html/rfc7889
[RFC 3348]: https://tools.ietf.org/html/rfc3348
[RFC 6851]: https://tools.ietf.org/html/rfc6851
[RFC 6154]: https://tools.ietf.org/html/rfc6154
[RFC 5255]: https://tools.ietf.org/html/rfc5255
[RFC 4978]: https://tools.ietf.org/html/rfc4978
[RFC 3691]: https://tools.ietf.org/html/rfc3691
[RFC 2177]: https://tools.ietf.org/html/rfc2177
[RFC 7888]: https://tools.ietf.org/html/rfc7888
[RFC 4959]: https://tools.ietf.org/html/rfc4959
[RFC 2033]: https://tools.ietf.org/html/rfc2033
[RFC 5321]: https://tools.ietf.org/html/rfc5321
[RFC 6409]: https://tools.ietf.org/html/rfc6409
[RFC 1870]: https://tools.ietf.org/html/rfc1870
[RFC 2920]: https://tools.ietf.org/html/rfc2920
[RFC 2034]: https://tools.ietf.org/html/rfc2034
[RFC 3207]: https://tools.ietf.org/html/rfc3207
[RFC 4954]: https://tools.ietf.org/html/rfc4954
[RFC 6152]: https://tools.ietf.org/html/rfc6152
[RFC 6531]: https://tools.ietf.org/html/rfc6531
[RFC 6522]: https://tools.ietf.org/html/rfc6522
[RFC 3464]: https://tools.ietf.org/html/rfc3464
[RFC 6533]: https://tools.ietf.org/html/rfc6533
[RFC 4422]: https://tools.ietf.org/html/rfc4422
[RFC 4616]: https://tools.ietf.org/html/rfc4616
[RFC 6376]: https://tools.ietf.org/html/rfc6376
[RFC 7001]: https://tools.ietf.org/html/rfc7001
[RFC 7208]: https://tools.ietf.org/html/rfc7208
[RFC 7372]: https://tools.ietf.org/html/rfc7372
[RFC 7479]: https://tools.ietf.org/html/rfc7479
[RFC 8301]: https://tools.ietf.org/html/rfc8301
[RFC 8463]: https://tools.ietf.org/html/rfc8463
[RFC 8616]: https://tools.ietf.org/html/rfc8616
[RFC 4033]: https://tools.ietf.org/html/rfc4033
[RFC 6698]: https://tools.ietf.org/html/rfc6698
[RFC 7672]: https://tools.ietf.org/html/rfc7672
[RFC 8461]: https://tools.ietf.org/html/rfc8461
[RFC 3492]: https://tools.ietf.org/html/rfc3492
[RFC 3629]: https://tools.ietf.org/html/rfc3629
[RFC 5890]: https://tools.ietf.org/html/rfc5890
[RFC 5891]: https://tools.ietf.org/html/rfc5891
[RFC 7616]: https://tools.ietf.org/html/rfc7616
[RFC 8264]: https://tools.ietf.org/html/rfc8264
[RFC 5782]: https://tools.ietf.org/html/rfc5782
[RFC 2822]: https://tools.ietf.org/html/rfc2822
[RFC 2045]: https://tools.ietf.org/html/rfc2045
[RFC 2046]: https://tools.ietf.org/html/rfc2046
[RFC 2047]: https://tools.ietf.org/html/rfc2047
[RFC 2048]: https://tools.ietf.org/html/rfc2048
[RFC 2049]: https://tools.ietf.org/html/rfc2049
[RFC 6532]: https://tools.ietf.org/html/rfc6532
[RFC 3501]: https://tools.ietf.org/html/rfc3501
[RFC 2595]: https://tools.ietf.org/html/rfc2595
[RFC 7889]: https://tools.ietf.org/html/rfc7889
[RFC 3348]: https://tools.ietf.org/html/rfc3348
[RFC 6851]: https://tools.ietf.org/html/rfc6851
[RFC 6154]: https://tools.ietf.org/html/rfc6154
[RFC 5255]: https://tools.ietf.org/html/rfc5255
[RFC 4978]: https://tools.ietf.org/html/rfc4978
[RFC 3691]: https://tools.ietf.org/html/rfc3691
[RFC 2177]: https://tools.ietf.org/html/rfc2177
[RFC 7888]: https://tools.ietf.org/html/rfc7888
[RFC 4959]: https://tools.ietf.org/html/rfc4959
[RFC 2033]: https://tools.ietf.org/html/rfc2033
[RFC 5321]: https://tools.ietf.org/html/rfc5321
[RFC 6409]: https://tools.ietf.org/html/rfc6409
[RFC 1870]: https://tools.ietf.org/html/rfc1870
[RFC 2920]: https://tools.ietf.org/html/rfc2920
[RFC 2034]: https://tools.ietf.org/html/rfc2034
[RFC 3207]: https://tools.ietf.org/html/rfc3207
[RFC 4954]: https://tools.ietf.org/html/rfc4954
[RFC 6152]: https://tools.ietf.org/html/rfc6152
[RFC 6531]: https://tools.ietf.org/html/rfc6531
[RFC 6522]: https://tools.ietf.org/html/rfc6522
[RFC 3464]: https://tools.ietf.org/html/rfc3464
[RFC 6533]: https://tools.ietf.org/html/rfc6533
[RFC 4422]: https://tools.ietf.org/html/rfc4422
[RFC 4616]: https://tools.ietf.org/html/rfc4616
[RFC 6376]: https://tools.ietf.org/html/rfc6376
[RFC 7001]: https://tools.ietf.org/html/rfc7001
[RFC 7208]: https://tools.ietf.org/html/rfc7208
[RFC 7372]: https://tools.ietf.org/html/rfc7372
[RFC 7479]: https://tools.ietf.org/html/rfc7479
[RFC 8301]: https://tools.ietf.org/html/rfc8301
[RFC 8463]: https://tools.ietf.org/html/rfc8463
[RFC 8616]: https://tools.ietf.org/html/rfc8616
[RFC 4033]: https://tools.ietf.org/html/rfc4033
[RFC 6698]: https://tools.ietf.org/html/rfc6698
[RFC 7672]: https://tools.ietf.org/html/rfc7672
[RFC 8461]: https://tools.ietf.org/html/rfc8461
[RFC 3492]: https://tools.ietf.org/html/rfc3492
[RFC 3629]: https://tools.ietf.org/html/rfc3629
[RFC 5890]: https://tools.ietf.org/html/rfc5890
[RFC 5891]: https://tools.ietf.org/html/rfc5891
[RFC 7616]: https://tools.ietf.org/html/rfc7616
[RFC 8264]: https://tools.ietf.org/html/rfc8264
[RFC 5782]: https://tools.ietf.org/html/rfc5782
[RFC 2822]: https://tools.ietf.org/html/rfc2822
[RFC 2045]: https://tools.ietf.org/html/rfc2045
[RFC 2046]: https://tools.ietf.org/html/rfc2046
[RFC 2047]: https://tools.ietf.org/html/rfc2047
[RFC 2048]: https://tools.ietf.org/html/rfc2048
[RFC 2049]: https://tools.ietf.org/html/rfc2049
[RFC 6532]: https://tools.ietf.org/html/rfc6532
[RFC 3501]: https://tools.ietf.org/html/rfc3501
[RFC 2595]: https://tools.ietf.org/html/rfc2595
[RFC 7889]: https://tools.ietf.org/html/rfc7889
[RFC 3348]: https://tools.ietf.org/html/rfc3348
[RFC 6851]: https://tools.ietf.org/html/rfc6851
[RFC 6154]: https://tools.ietf.org/html/rfc6154
[RFC 5255]: https://tools.ietf.org/html/rfc5255
[RFC 4978]: https://tools.ietf.org/html/rfc4978
[RFC 3691]: https://tools.ietf.org/html/rfc3691
[RFC 2177]: https://tools.ietf.org/html/rfc2177
[RFC 7888]: https://tools.ietf.org/html/rfc7888
[RFC 4959]: https://tools.ietf.org/html/rfc4959
[RFC 2033]: https://tools.ietf.org/html/rfc2033
[RFC 5321]: https://tools.ietf.org/html/rfc5321
[RFC 6409]: https://tools.ietf.org/html/rfc6409
[RFC 1870]: https://tools.ietf.org/html/rfc1870
[RFC 2920]: https://tools.ietf.org/html/rfc2920
[RFC 2034]: https://tools.ietf.org/html/rfc2034
[RFC 3207]: https://tools.ietf.org/html/rfc3207
[RFC 4954]: https://tools.ietf.org/html/rfc4954
[RFC 6152]: https://tools.ietf.org/html/rfc6152
[RFC 6531]: https://tools.ietf.org/html/rfc6531
[RFC 6522]: https://tools.ietf.org/html/rfc6522
[RFC 3464]: https://tools.ietf.org/html/rfc3464
[RFC 6533]: https://tools.ietf.org/html/rfc6533
[RFC 4422]: https://tools.ietf.org/html/rfc4422
[RFC 4616]: https://tools.ietf.org/html/rfc4616
[RFC 6376]: https://tools.ietf.org/html/rfc6376
[RFC 8301]: https://tools.ietf.org/html/rfc8301
[RFC 8463]: https://tools.ietf.org/html/rfc8463
[RFC 7208]: https://tools.ietf.org/html/rfc7208
[RFC 7372]: https://tools.ietf.org/html/rfc7372
[RFC 7479]: https://tools.ietf.org/html/rfc7479
[RFC 8616]: https://tools.ietf.org/html/rfc8616
[RFC 4033]: https://tools.ietf.org/html/rfc4033
[RFC 6698]: https://tools.ietf.org/html/rfc6698
[RFC 7672]: https://tools.ietf.org/html/rfc7672
[RFC 8461]: https://tools.ietf.org/html/rfc8461
[RFC 3492]: https://tools.ietf.org/html/rfc3492
[RFC 3629]: https://tools.ietf.org/html/rfc3629
[RFC 5890]: https://tools.ietf.org/html/rfc5890
[RFC 5891]: https://tools.ietf.org/html/rfc5891
[RFC 7616]: https://tools.ietf.org/html/rfc7616
[RFC 8264]: https://tools.ietf.org/html/rfc8264
[RFC 5782]: https://tools.ietf.org/html/rfc5782

[Unicode 11.0.0]: https://www.unicode.org/versions/components-11.0.0.html
[UAX #15]: https://unicode.org/reports/tr15/


================================================
FILE: docs/internals/sqlite.md
================================================
# maddy & SQLite

SQLite is a perfect choice for small deployments because no additional
configuration is required to get started. It is recommended for cases when you
have less than 10 mail accounts.

**Note: SQLite requires DB-wide locking for writing, it means that multiple
messages can't be accepted in parallel. This is not the case for server-based
RDBMS where maddy can accept multiple messages in parallel even for a single
mailbox.**

## WAL mode

maddy forces WAL journal mode for SQLite. This makes things reasonably fast and
reduces locking contention which may be important for a typical mail server.

maddy uses increased WAL autocheckpoint interval. This means that while
maintaining a high write throughput, maddy will have to stop for a bit (0.5-1
second) every time 78 MiB is written to the DB (with default configuration it
is 15 MiB).

Note that when moving the database around you need to move WAL journal (`-wal`)
and shared memory (`-shm`) files as well, otherwise some changes to the DB will
be lost.

## Query planner statistics

maddy updates query planner statistics on shutdown and every 5 hours. It
provides query planner with information to access the database in more
efficient way because go-imap-sql schema does use a few so called "low-quality
indexes".

## Auto-vacuum

maddy turns on SQLite auto-vacuum feature. This means that database file size
will shrink when data is removed (compared to default when it remains unused).

## Manual vacuuming

Auto-vacuuming can lead to database fragmentation and thus reduce the read
performance.  To do manual vacuum operation to repack and defragment database
file, install the SQLite3 console utility and run the following commands:
```
sqlite3 -cmd 'vacuum' database_file_path_here.db
sqlite3 -cmd 'pragma wal_checkpoint(truncate)' database_file_path_here.db
```

It will take some time to complete, you can close the utility when the
`sqlite>` prompt appears.


================================================
FILE: docs/internals/unicode.md
================================================
# Unicode support

maddy has the first-class Unicode support in all components (modules). You do
not have to take any actions to make it work with internationalized domains,
mailbox names or non-ASCII message headers.

Internally, all text fields in maddy are represented in UTF-8 and handled using
Unicode-aware operations for comparisons, case-folding and so on.

## Non-ASCII data in message headers and bodies

maddy SMTP implementation does not care about encodings used in MIME headers or
in `Content-Type text/*` charset field.

However, local IMAP storage implementation needs to perform certain operations
on header contents. This is mostly about SEARCH functionality. For IMAP search
to work correctly, the message body and headers should use one of the following
encodings:

- ASCII
- UTF-8
- ISO-8859-1, 2, 3, 4, 9, 10, 13, 14, 15 or 16
- Windows-1250, 1251 or 1252 (aka Code Page 1250 and so on)
- KOI8-R
- ~~HZGB2312~~, GB18030
- GBK (aka Code Page 936)
- Shift JIS (aka Code Page 932 or Windows-31J)
- Big-5 (aka Code Page 950)
- EUC-JP
- ISO-2022-JP

_Support for HZGB2312 is currently disabled due to bugs with security
implications._

If mailbox includes a message with any encoding not listed here, it will not
be returned in search results for any request.

Behavior regarding handling of non-Unicode encodings is not considered stable
and may change between versions (including removal of supported encodings). If
you need your stuff to work correctly - start using UTF-8.

## Configuration files

maddy configuration files are assumed to be encoded in UTF-8. Use of any other
encoding will break stuff, do not do it.

Domain names (e.g. in hostname directive or pipeline rules) can be represented
using the ACE form (aka Punycode). They will be converted to the Unicode form
internally.

## Local credentials

'sql' storage backend and authentication provider enforce a number of additional
constraints on used account names.

PRECIS UsernameCaseMapped profile is enforced for local email addresses.
It limits the use of control and Bidi characters to make sure the used value
can be represented consistently in a variety of contexts. On top of that, the
address is case-folded and normalized to the NFC form for consistent internal
handling.

PRECIS OpaqueString profile is enforced for passwords. Less strict rules are
applied here. Runs of Unicode whitespace characters are replaced with a single
ASCII space. NFC normalization is applied afterwards. If the resulting string
is empty - the password is not accepted.

Both profiles are defined in RFC 8265, consult it for details.

## Protocol support

### SMTPUTF8 extension

maddy SMTP implementation includes support for the SMTPUTF8 extension as
defined in RFC 6531.

This means maddy can handle internationalized mailbox and domain names in MAIL
FROM, RCPT TO commands both for outbound and inbound delivery.

maddy will not accept messages with non-ASCII envelope addresses unless
SMTPUTF8 support is requested. If a message with SMTPUTF8 flag set is forwarded
to a server without SMTPUTF8 support, delivery will fail unless it is possible
to represent envelope addresses in the ASCII form (only domains use Unicode and
they can be converted to Punycode). Contents of message body (and header) are
not considered and always accepted and sent as-is, no automatic downgrading or
reencoding is done.

### IMAP UTF8, I18NLEVEL extensions

Currently, maddy does not include support for UTF8 and I18NLEVEL IMAP
extensions. However, it is not a problem that can prevent it from correctly
handling UTF-8 messages (or even messages in other non-ASCII encodings
mentioned above).

Clients that want to implement proper handling for Unicode strings may assume
maddy does not handle them properly in e.g. SEARCH commands and so such clients
may download messages and process them locally.


================================================
FILE: docs/man/.gitignore
================================================
_generated_*.md


================================================
FILE: docs/man/README.md
================================================
maddy manual pages
-------------------

The reference documentation is maintained in the scdoc format and is compiled
into a set of Unix man pages viewable using the standard `man` utility.

See https://git.sr.ht/~sircmpwn/scdoc for information about the tool used to
build pages.
It can be used as follows:
```
scdoc < maddy-filters.5.scd > maddy-filters.5
man ./maddy-filters.5
```

build.sh script in the repo root compiles and installs man pages if the scdoc
utility is installed in the system.


================================================
FILE: docs/man/maddy.1.scd
================================================
maddy(1) "maddy mail server" "maddy reference documentation"

; TITLE Command line arguments

# Name

maddy - Composable all-in-one mail server.

# Synopsis

*maddy* [options...]

# Description

Maddy is Mail Transfer agent (MTA), Mail Delivery Agent (MDA), Mail Submission
Agent (MSA), IMAP server and a set of other essential protocols/schemes
necessary to run secure email server implemented in one executable.

# Command line arguments

*-h, -help*
	Show help message and exit.

*-config* _path_
	Path to the configuration file. Default is /etc/maddy/maddy.conf.

*-libexec* _path_
	Path to the libexec directory. Helper executables will be searched here.
	Default is /usr/lib/maddy.

*-log* _targets..._
	Comma-separated list of logging targets. Valid values are the same as the
	'log' config directive. Affects logging before configuration parsing
	completes and after it, if the different value is not specified in the
	configuration.

*-debug*
	Enable debug log. You want to use it when reporting bugs.

*-v*
	Print version & build metadata.


================================================
FILE: docs/man/prepare_md.py
================================================
#!/usr/bin/python3

"""
This script does all necessary pre-processing to convert scdoc format into
Markdown.

Usage:
    prepare_md.py < in > out
    prepare_md.py file1 file2 file3
        Converts into _generated_file1.md, etc.
"""

import sys
import re

anchor_escape = str.maketrans(r' #()./\+-_', '__________')

def prepare(r, w):
    new_lines = list()
    title = str()
    previous_h1_anchor = ''

    inside_literal = False

    for line in r:
        if not inside_literal:
            if line.startswith('; TITLE ') and title == '':
                title = line[8:]
            if line[0] == ';':
                continue
            # turn *page*(1) into [**page(1)**](../_generated_page.1)
            line = re.sub(r'\*(.+?)\*\(([0-9])\)', r'[*\1(\2)*](../_generated_\1.\2)', line)
            # *aaa* => **aaa**
            line = re.sub(r'\*(.+?)\*', r'**\1**', line)
            # remove ++ from line endings
            line = re.sub(r'\+\+$', '<br>', line)
            # turn whatever looks like a link into one
            line = re.sub(r'(https://[^ \)\(\\]+[a-z0-9_\-])', r'[\1](\1)', line)
            # escape underscores inside words
            line = re.sub(r'([^ ])_([^ ])', r'\1\\_\2', line)

        if line.startswith('```'):
            inside_literal = not inside_literal

        new_lines.append(line)

    if title != '':
        print('#', title, file=w)

    print(''.join(new_lines[1:]), file=w)

if len(sys.argv) == 1:
    prepare(sys.stdin, sys.stdout)
else:
    for f in sys.argv[1:]:
        new_name = '_generated_' + f[:-4] + '.md'
        prepare(open(f, 'r'), open(new_name, 'w'))


================================================
FILE: docs/multiple-domains.md
================================================
# Multiple domains configuration

By default, maddy uses email addresses as account identifiers for both
authentication and storage purposes. Therefore, account named `user@example.org`
is completely independent from `user@example.com`. They must be created
separately, may have different credentials and have separate IMAP mailboxes.

This makes it extremely easy to setup maddy to manage multiple otherwise
independent domains.

Default configuration file contains two macros - `$(primary_domain)` and
`$(local_domains)`. They are used to used in several places thorough the
file to configure message routing, security checks, etc.

In general, you should just add all domains you want maddy to manage to
`$(local_domains)`, like this:
```
$(primary_domain) = example.org
$(local_domains) = $(primary_domain) example.com
```
Note that you need to pick one domain as a "primary" for use in
auto-generated messages.

With that done, you can create accounts using both domains in the name, send
and receive messages and so on.  Do not forget to configure corresponding SPF,
DMARC and MTA-STS records as was recommended in
the [introduction tutorial](tutorials/setting-up.md).

Also note that you do not really need a separate TLS certificate for each
managed domain. You can have one hostname e.g. mail.example.org set as an MX
record for multiple domains.

**If you want multiple domains to share username namespace**, you should change
several more options.

You can make "user@example.org" and "user@example.com" users share the same
credentials of user "user" but have different IMAP mailboxes ("user@example.org"
and "user@example.com" correspondingly). For that, it is enough to set `auth_map`
globally to use `email_localpart` table:
```
auth_map email_localpart
```
This way, when user logs in as "user@example.org", "user" will be passed
to the authentication provider, but "user@example.org" will be passed to the
storage backend. You should create accounts like this:
```
maddy creds create user
maddy imap-acct create user@example.org
maddy imap-acct create user@example.com
```

**If you want accounts to also share the same IMAP storage of account named
"user"**, you can set `storage_map` in IMAP endpoint and `delivery_map` in
storage backend to use `email_locapart`:
```
storage.imapsql local_mailboxes {
   ...
   delivery_map email_localpart # deliver "user@*" to "user"
}
imap tls://0.0.0.0:993 {
   ...
   storage &local_mailboxes
   ...
   storage_map email_localpart # "user@*" accesses "user" mailbox
}
```

You also might want to make it possible to log in without
specifying a domain at all. In this case, use `email_localpart_optional` for
both `auth_map` and `storage_map`.

You also need to make `authorize_sender` check (used in `submission` endpoint)
accept non-email usernames:
```
authorize_sender {
  ...
  user_to_email chain {
    step email_localpart_optional           # remove domain from username if present
    step email_with_domain $(local_domains) # expand username with all allowed domains
  }
}
```

## TL;DR

Your options:

**"user@example.org" and "user@example.com" have distinct credentials and
distinct mailboxes.**

```
$(primary_domain) = example.org
$(local_domains) = example.org example.com
```

Create accounts as:

```shell
maddy creds create user@example.org
maddy imap-acct create user@example.org
maddy creds create user@example.com
maddy imap-acct create user@example.com
```

**"user@example.org" and "user@example.com" have same credentials but
distinct mailboxes.**

```
$(primary_domain) = example.org
$(local_domains) = example.org example.com
auth_map email_localpart
```

Create accounts as:
```shell
maddy creds create user
maddy imap-acct create user@example.org
maddy imap-acct create user@example.com
```

**"user@example.org", "user@example.com", "user" have same credentials and same
mailboxes.**

```
   $(primary_domain) = example.org
   $(local_domains) = example.org example.com
   auth_map email_localpart_optional # authenticating as "user@*" checks credentials for "user"

   storage.imapsql local_mailboxes {
      ...
      delivery_map email_localpart_optional # deliver "user@*" to "user" mailbox
   }

   imap tls://0.0.0.0:993 {
      ...
      storage_map email_localpart_optional # authenticating as "user@*" accesses "user" mailboxes
   }

   submission tls://0.0.0.0:465 {
      check {
        authorize_sender {
          ...
          user_to_email chain {
            step email_localpart_optional           # remove domain from username if present
            step email_with_domain $(local_domains) # expand username with all allowed domains
          }
        }
      }
      ...
   }
```

Create accounts as:
```shell
maddy creds create user
maddy imap-acct create user
```


================================================
FILE: docs/reference/auth/dovecot_sasl.md
================================================
# Dovecot SASL

The 'auth.dovecot_sasl' module implements the client side of the Dovecot
authentication protocol, allowing maddy to use it as a credentials source.

Currently SASL mechanisms support is limited to mechanisms supported by maddy
so you cannot get e.g. SCRAM-MD5 this way.

```
auth.dovecot_sasl {
	endpoint unix://socket_path
}

dovecot_sasl unix://socket_path
```

## Configuration directives

### endpoint _schema://address_
Default: not set

Set the address to use to contact Dovecot SASL server in the standard endpoint
format.

`tcp://10.0.0.1:2222` for TCP, `unix:///var/lib/dovecot/auth.sock` for Unix
domain sockets.


================================================
FILE: docs/reference/auth/external.md
================================================
# System command

auth.external module for authentication using external helper binary. It looks for binary
named `maddy-auth-helper` in $PATH and libexecdir and uses it for authentication
using username/password pair.

The protocol is very simple:
Program is launched for each authentication. Username and password are written
to stdin, adding \n to the end. If binary exits with 0 status code -
authentication is considered successful. If the status code is 1 -
authentication is failed. If the status code is 2 - another unrelated error has
happened. Additional information should be written to stderr.

```
auth.external {
    helper /usr/bin/ldap-helper
    perdomain no
    domains example.org
}
```

## Configuration directives

### helper _file_path_

**Required.** <br>
Location of the helper binary. 

---

### perdomain _boolean_
Default: `no`

Don't remove domain part of username when authenticating and require it to be
present. Can be used if you want user@domain1 and user@domain2 to be different
accounts.

---

### domains _domains..._
Default: not specified

Domains that should be allowed in username during authentication.

For example, if 'domains' is set to "domain1 domain2", then
username, username@domain1 and username@domain2 will be accepted as valid login
name in addition to just username.

If used without 'perdomain', domain part will be removed from login before
check with underlying auth. mechanism. If 'perdomain' is set, then
domains must be also set and domain part **will not** be removed before check.



================================================
FILE: docs/reference/auth/ldap.md
================================================
# LDAP BindDN

maddy supports authentication via LDAP using DN binding. Passwords are verified
by the LDAP server.

maddy needs to know the DN to use for binding. It can be obtained either by
directory search or template .

Note that storage backends conventionally use email addresses, if you use
non-email identifiers as usernames then you should map them onto
emails on delivery by using `auth_map` (see documentation page for used storage backend).

auth.ldap also can be a used as a table module. This way you can check
whether the account exists. It works only if DN template is not used.

```
auth.ldap {
    urls ldap://maddy.test:389

    # Specify initial bind credentials. Not required ('bind off')
    # if DN template is used.
    bind plain "cn=maddy,ou=people,dc=maddy,dc=test" "123456"

    # Specify DN template to skip lookup.
    dn_template "cn={username},ou=people,dc=maddy,dc=test"

    # Specify base_dn and filter to lookup DN.
    base_dn "ou=people,dc=maddy,dc=test"
    filter "(&(objectClass=posixAccount)(uid={username}))"

    tls_client { ... }
    starttls off
    debug off
    connect_timeout 1m
}
```
```
auth.ldap ldap://maddy.test.389 {
    ...
}
```

## Configuration directives

### urls _servers..._

**Required.**

URLs of the directory servers to use. First available server
is used - no load-balancing is done.

URLs should use `ldap://`, `ldaps://`, `ldapi://` schemes.

---

### bind `off` | `unauth` | `external` | `plain` _username_ _password_

Default: `off`

Credentials to use for initial binding. Required if DN lookup is used.

`unauth` performs unauthenticated bind. `external` performs external binding
which is useful for Unix socket connections (`ldapi://`) or TLS client certificate
authentication (cert. is set using tls_client directive). `plain` performs a
simple bind using provided credentials.

---

### dn_template _template_

DN template to use for binding. `{username}` is replaced with the
username specified by the user.

---

### base_dn _dn_

Base DN to use for lookup.

---

### filter _str_

DN lookup filter. `{username}` is replaced with the username specified
by the user.

Example:

```
(&(objectClass=posixAccount)(uid={username}))
```

Example (using ActiveDirectory):

```
(&(objectCategory=Person)(memberOf=CN=user-group,OU=example,DC=example,DC=org)(sAMAccountName={username})(!(UserAccountControl:1.2.840.113556.1.4.803:=2)))
```

Example:

```
(&(objectClass=Person)(mail={username}))
```

---

### starttls _bool_
Default: `off`

Whether to upgrade connection to TLS using STARTTLS.

---

### tls_client { ... }

Advanced TLS client configuration. See [TLS configuration / Client](/reference/tls/#client) for details.

---

### connect_timeout _duration_
Default: `1m`

Timeout for initial connection to the directory server.

---

### request_timeout _duration_
Default: `1m`

Timeout for each request (binding, lookup).


================================================
FILE: docs/reference/auth/netauth.md
================================================
# Native NetAuth

maddy supports authentication via NetAuth using direct entity
authentication checks.  Passwords are verified by the NetAuth server.

maddy needs to know the Entity ID to use for authentication.  It must
match the string the user provides for the Local Atom part of their
mail address.

Note that storage backends conventionally use email addresses.  Since NetAuth
recommends *nix compatible usernames. You will need to either map email
identifiers specified by user to NetAuth Entity IDs using `auth_map` in
endpoint.smtp/imap configuration (recommended) or you would need to use
`storage_map` in storage backend configuration to map NetAuth Entity ID
specified by user back to appropriate storage backend account names.

auth.netauth also can be used as a table module.  This way you can
check whether the account exists.

Note that the configuration fragment provided below is very sparse.
This is because NetAuth expects to read most of its common
configuration values from the system NetAuth config file located at
`/etc/netauth/config.toml`.

```
auth.netauth {
  require_group "maddy-users"
  debug off
}
```

```
auth.netauth {}
```

## Configuration directives

### require_group _group_

Optional.

Group that entities must possess to be able to use maddy services.
This can be used to provide email to just a subset of the entities
present in NetAuth.

---

### debug `on` | `off`

Default: `off`


================================================
FILE: docs/reference/auth/pam.md
================================================
# PAM

auth.pam module implements authentication using libpam. Alternatively it can be configured to
use helper binary like auth.external module does.

maddy should be built with libpam build tag to use this module without
'use_helper' directive.

```
go get -tags 'libpam' ...
```

```
auth.pam {
    debug no
    use_helper no
}
```

## Configuration directives

### debug _boolean_ 
Default: `no`

Enable verbose logging for all modules. You don't need that unless you are
reporting a bug.

---

### use_helper _boolean_
Default: `no`

Use `LibexecDirectory/maddy-pam-helper` instead of directly calling libpam.
You need to use that if:

1. maddy is not compiled with libpam, but `maddy-pam-helper` is built separately.
2. maddy is running as an unprivileged user and used PAM configuration requires additional privileges (e.g. when using system accounts).

For 2, you need to make `maddy-pam-helper` binary setuid, see
README.md in source tree for details.

TL;DR (assuming you have the maddy group):

```
chown root:maddy /usr/lib/maddy/maddy-pam-helper
chmod u+xs,g+x,o-x /usr/lib/maddy/maddy-pam-helper
```



================================================
FILE: docs/reference/auth/pass_table.md
================================================
# Password table

auth.pass_table module implements username:password authentication by looking up the
password hash using a table module (maddy-tables(5)). It can be used
to load user credentials from text file (via table.file module) or SQL query
(via table.sql_table module).


Definition:
```
auth.pass_table [block name] {
	table <table config>

}
```
Shortened variant for inline use:
```
pass_table <table> [table arguments] {
	[additional table config]
}
```

Example, read username:password pair from the text file:
```
smtp tcp://0.0.0.0:587 {
	auth pass_table file /etc/maddy/smtp_passwd
	...
}
```

## Password hashes

pass_table expects the used table to contain certain structured values with
hash algorithm name, salt and other necessary parameters.

You should use `maddy hash` command to generate suitable values.
See `maddy hash --help` for details.

## maddy creds

If the underlying table is a "mutable" table (see maddy-tables(5)) then
the `maddy creds` command can be used to modify the underlying tables
via pass_table module. It will act on a "local credentials store" and will write
appropriate hash values to the table.


================================================
FILE: docs/reference/auth/plain_separate.md
================================================
# Separate username and password lookup

auth.plain_separate module implements authentication using username:password pairs but can
use zero or more "table modules" (maddy-tables(5)) and one or more
authentication providers to verify credentials.

```
auth.plain_separate {
	user ...
	user ...
	...
	pass ...
	pass ...
	...
}
```

How it works:
- Initial username input is normalized using PRECIS UsernameCaseMapped profile.
- Each table specified with the 'user' directive looked up using normalized
  username. If match is not found in any table, authentication fails.
- Each authentication provider specified with the 'pass' directive is tried.
  If authentication with all providers fails - an error is returned.

## Configuration directives

### user _table-module_

Configuration block for any module from maddy-tables(5) can be used here.

Example:

```
user file /etc/maddy/allowed_users
```

---

### pass _auth-provider_

Configuration block for any auth. provider module can be used here, even
'plain_split' itself.

The used auth. provider must provide username:password pair-based
authentication.


================================================
FILE: docs/reference/auth/shadow.md
================================================
# /etc/shadow

auth.shadow module implements authentication by reading /etc/shadow. Alternatively it can be
configured to use helper binary like auth.external does.

```
auth.shadow {
    debug no
    use_helper no
}
```

## Configuration directives

### debug _boolean_

Default: `no`

Enable verbose logging for all modules. You don't need that unless you are
reporting a bug.

---

### use_helper _boolean_
Default: `no`

Use `LibexecDirectory/maddy-shadow-helper` instead of directly reading `/etc/shadow`.
You need to use that if maddy is running as an unprivileged user
privileges (e.g. when using system accounts).

You need to make `maddy-shadow-helper` binary setuid, see
cmd/maddy-shadow-helper/README.md in source tree for details.

TL;DR (assuming you have maddy group):

```
chown root:maddy /usr/lib/maddy/maddy-shadow-helper
chmod u+xs,g+x,o-x /usr/lib/maddy/maddy-shadow-helper
```



================================================
FILE: docs/reference/blob/fs.md
================================================
# Filesystem

This module stores message bodies in a file system directory.

```
storage.blob.fs {
    root <directory>
}
```

```
storage.blob.fs <directory>
```

## Configuration directives

### root _path_
Default: not set

Path to the FS directory. Must be readable and writable by the server process.
If it does not exist - it will be created (parent directory should be writable
for this). Relative paths are interpreted relatively to server state directory.



================================================
FILE: docs/reference/blob/s3.md
================================================
# Amazon S3

storage.blob.s3 module stores messages bodies in a bucket on S3-compatible storage.

```
storage.blob.s3 {
    endpoint play.min.io
    secure yes
    access_key "Q3AM3UQ867SPQQA43P2F"
    secret_key "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG"
    bucket maddy-test

    # optional
    region eu-central-1
    object_prefix maddy/
    creds access_key
}
```

Example:

```
storage.imapsql local_mailboxes {
    ...
    msg_store s3 {
        endpoint s3.amazonaws.com
        access_key "..."
        secret_key "..."
        bucket maddy-messages
        region us-west-2
        creds access_key
    }
}
```

## Configuration directives

### endpoint _address:port_

**Required**.

Root S3 endpoint. e.g. `s3.amazonaws.com`

---

### secure _boolean_
Default: `yes`

Whether TLS should be used.

---

### access_key _string_<br>secret_key _string_

**Required**.

Static S3 credentials.

---

### bucket _name_

**Required**.

S3 bucket name. The bucket must exist and
be read-writable.

---

### region _string_
Default: not set

S3 bucket location. May be called "endpoint" in some manuals.

---

### object_prefix _string_
Default: empty string

String to add to all keys stored by maddy.

Can be useful when S3 is used as a file system.

---

### creds `access_key` | `file_minio` | `file_aws` | `iam`
Default: `access_key`

Credentials to use for accessing the S3 Bucket.

Credential Types:

 - `access_key`: use AWS access key and secret access key 
 - `file_minio`: use credentials for Minio present at ~/.mc/config.json
 - `file_aws`: use credentials for AWS S3 present at ~/.aws/credentials
 - `iam`: use AWS IAM instance profile for credentials.

By default, access_key is used with the access key and secret access key present in the config.


================================================
FILE: docs/reference/checks/actions.md
================================================
# Check actions

When a certain check module thinks the message is "bad", it takes some actions
depending on its configuration. Most checks follow the same configuration
structure and allow following actions to be taken on check failure:

- Do nothing (`action ignore`)

Useful for testing deployment of new checks. Check failures are still logged
but they have no effect on message delivery.

- Reject the message (`action reject`)

Reject the message at connection time. No bounce is generated locally.

- Quarantine the message (`action quarantine`)

Mark message as 'quarantined'. If message is then delivered to the local
storage, the storage backend can place the message in the 'Junk' mailbox.
Another thing to keep in mind that 'target.remote' module
will refuse to send quarantined messages.

================================================
FILE: docs/reference/checks/authorize_sender.md
================================================
# MAIL FROM and From authorization

Module check.authorize_sender verifies that envelope and header sender addresses belong
to the authenticated user. Address ownership is established via table
that maps each user account to a email address it is allowed to use.
There are some special cases, see `user_to_email` description below.

```
check.authorize_sender {
    prepare_email identity
    user_to_email identity
    check_header yes

    unauth_action reject
    no_match_action reject
    malformed_action reject
    err_action reject

    auth_normalize auto
    from_normalize auto
}
```
```
check {
    authorize_sender { ... }
}
```

## Configuration directives

### user_to_email _table_
Default: `identity`

Table that maps authorization username to the list of sender emails
the user is allowed to use.

In additional to email addresses, the table can contain domain names or
special string "\*" as a value. If the value is a domain - user
will be allowed to use any mailbox within it as a sender address.
If it is "\*" - user will be allowed to use any address.

By default, table.identity is used, meaning that username should
be equal to the sender email.

Before username is looked up via the table, normalization algorithm
defined by auth_normalize is applied to it.

---

### prepare_email _table_
Default: `identity`

Table that is used to translate email addresses before they
are matched against user_to_email values.

Typically used to allow users to use their aliases as sender
addresses - prepare_email in this case should translate
aliases to "canonical" addresses. This is how it is
done in default configuration.

If table does not contain any mapping for the used sender
address, it will be used as is.

---

### check_header _boolean_
Default: `yes`

Whether to verify header sender in addition to envelope.

Either Sender or From field value should match the
authorization identity.

---

### unauth_action _action_
Default: `reject`

What to do if the user is not authenticated at all.

---

### no_match_action _action_
Default: `reject`

What to do if user is not allowed to use the sender address specified.

---

### malformed_action _action_
Default: `reject`

What to do if From or Sender header fields contain malformed values.

---

### err_action _action_
Default: `reject`

What to do if error happens during prepare_email or user_to_email lookup.

---

### auth_normalize _action_
Default: `auto`

Normalization function to apply to authorization username before
further processing.

Available options:

- `auto`                    `precis_casefold_email` for valid emails, `precis_casefold` otherwise.
- `precis_casefold_email`   PRECIS UsernameCaseMapped profile + U-labels form for domain
- `precis_casefold`         PRECIS UsernameCaseMapped profile for the entire string
- `precis_email`            PRECIS UsernameCasePreserved profile + U-labels form for domain
- `precis`                  PRECIS UsernameCasePreserved profile for the entire string
- `casefold`                Convert to lower case
- `noop`                    Nothing

PRECIS profiles are defined by RFC 8265. In short, they make sure
that Unicode strings that look the same will be compared as if they were
the same. CaseMapped profiles also convert strings to lower case.

---

### from_normalize _action_
Default: `auto`

Normalization function to apply to email addresses before
further processing.

Available options are same as for `auth_normalize`.


================================================
FILE: docs/reference/checks/command.md
================================================
# System command filter

This module executes an arbitrary system command during a specified stage of
checks execution.

```
command executable_name arg0 arg1 ... {
	run_on body

	code 1 reject
	code 2 quarantine
}
```

## Arguments

The module arguments specify the command to run. If the first argument is not
an absolute path, it is looked up in the Libexec Directory (/usr/lib/maddy on
Linux) and in $PATH (in that ordering). Note that no additional handling
of arguments is done, especially, the command is executed directly, not via the
system shell.

There is a set of special strings that are replaced with the corresponding
message-specific values:

- `{source_ip}` – IPv4/IPv6 address of the sending MTA.
- `{source_host}` – Hostname of the sending MTA, from the HELO/EHLO command.
- `{source_rdns}` – PTR record of the sending MTA IP address.
- `{msg_id}` – Internal message identifier. Unique for each delivery.
- `{auth_user}` – Client username, if authenticated using SASL PLAIN
- `{sender}` – Message sender address, as specified in the MAIL FROM SMTP command.
- `{rcpts}` – List of accepted recipient addresses, including the currently handled
  one.
- `{address}` – Currently handled address. This is a recipient address if the command
  is called during RCPT TO command handling (`run_on rcpt`) or a sender
  address if the command is called during MAIL FROM command handling (`run_on
  sender`).

If value is undefined (e.g. `{source_ip}` for a message accepted over a Unix
socket) or unavailable (the command is executed too early), the placeholder
is replaced with an empty string. Note that it can not remove the argument.
E.g. `-i {source_ip}` will not become just `-i`, it will be `-i ""`

Undefined placeholders are not replaced.

## Command stdout

The command stdout must be either empty or contain a valid RFC 5322 header.
If it contains a byte stream that does not look a valid header, the message
will be rejected with a temporary error.

The header from stdout will be **prepended** to the message header.

## Configuration directives

### run_on `conn` | `sender` | `rcpt` | `body`
Default: `body`

When to run the command. This directive also affects the information visible
for the message.

- `conn`<br>
    Run before the sender address (MAIL FROM) is handled.<br>
    **Stdin**: Empty <br>
    **Available placeholders**: {source_ip}, {source_host}, {msg_id}, {auth_user}.

- `sender`<br>
    Run during sender address (MAIL FROM) handling.<br>
    **Stdin**: Empty <br>
    **Available placeholders**: conn placeholders + {sender}, {address}.
    The {address} placeholder contains the MAIL FROM address.

- `rcpt`<br>
    Run during recipient address (RCPT TO) handling. The command is executed
    once for each RCPT TO command, even if the same recipient is specified
    multiple times.<br>
    **Stdin**: Empty <br>
    **Available placeholders**: sender placeholders + {rcpts}.
    The {address} placeholder contains the recipient address.

- `body`<br>
    Run during message body handling.<br>
    **Stdin**: The message header + body <br>
    **Available placeholders**: all except for {address}.

---

### code _integer_ ignore <br>code _integer_ quarantine <br>code _integer_ reject _smtp-code_ _smtp-enhanced-code_ _smtp-message_

This directive specifies the mapping from the command exit code _integer_ to
the message pipeline action.

Two codes are defined implicitly, exit code 1 causes the message to be rejected
with a permanent error, exit code 2 causes the message to be quarantined. Both
actions can be overridden using the 'code' directive.



================================================
FILE: docs/reference/checks/dkim.md
================================================
# DKIM

This is the check module that performs verification of the DKIM signatures
present on the incoming messages.

## Configuration directives

```
check.dkim {
    debug no
    required_fields From Subject
    allow_body_subset no
    no_sig_action ignore
    broken_sig_action ignore
	fail_open no
}
```

### debug _boolean_
Default: global directive value

Log both successful and unsuccessful check executions instead of just
unsuccessful.

---

### required_fields _string..._
Default: `From Subject`

Header fields that should be included in each signature. If signature
lacks any field listed in that directive, it will be considered invalid.

Note that From is always required to be signed, even if it is not included in
this directive.

---

### no_sig_action _action_
Default: `ignore` (recommended by RFC 6376)

Action to take when message without any signature is received.

Note that DMARC policy of the sender domain can request more strict handling of
missing DKIM signatures.

---

### broken_sig_action _action_
Default: `ignore` (recommended by RFC 6376)

Action to take when there are not valid signatures in a message.

Note that DMARC policy of the sender domain can request more strict handling of
broken DKIM signatures.

---

### fail_open _boolean_
Default: `no`

Whether to accept the message if a temporary error occurs during DKIM
verification. Rejecting the message with a 4xx code will require the sender
to resend it later in a hope that the problem will be resolved.


================================================
FILE: docs/reference/checks/dnsbl.md
================================================
# DNSBL lookup

The check.dnsbl module implements checking of source IP and hostnames against a set
of DNS-based Blackhole lists (DNSBLs).

Its configuration consists of module configuration directives and a set
of blocks specifying lists to use and kind of lookups to perform on them.

```
check.dnsbl {
    debug no
    check_early no

    quarantine_threshold 1
    reject_threshold 1

    # Lists configuration example.
    dnsbl.example.org {
        client_ipv4 yes
        client_ipv6 no
        ehlo no
        mailfrom no
        score 1
    }
    hsrbl.example.org {
        client_ipv4 no
        client_ipv6 no
        ehlo yes
        mailfrom yes
        score 1
    }
    
    # Example with per-response-code scoring (new in 0.8)
    zen.spamhaus.org {
        client_ipv4 yes
        client_ipv6 yes
        
        # SBL - Spamhaus Block List (known spam sources)
        response 127.0.0.2 127.0.0.3 {
            score 10
            message "Listed in Spamhaus SBL. See https://check.spamhaus.org/"
        }
        
        # XBL - Exploits Block List (compromised hosts)
        response 127.0.0.4 127.0.0.5 127.0.0.6 127.0.0.7 {
            score 10
            message "Listed in Spamhaus XBL. See https://check.spamhaus.org/"
        }
        
        # PBL - Policy Block List (dynamic IPs)
        response 127.0.0.10 127.0.0.11 {
            score 5
            message "Listed in Spamhaus PBL. See https://check.spamhaus.org/"
        }
    }
}
```

## Arguments

Arguments specify the list of IP-based BLs to use.

The following configurations are equivalent.

```
check {
    dnsbl dnsbl.example.org dnsbl2.example.org
}
```

```
check {
    dnsbl {
        dnsbl.example.org dnsbl2.example.org {
            client_ipv4 yes
            client_ipv6 no
            ehlo no
            mailfrom no
            score 1
        }
    }
}
```

## Configuration directives

### debug _boolean_
Default: global directive value

Enable verbose logging.

---

### check_early _boolean_
Default: `no`

Check BLs before mail delivery starts and silently reject blacklisted clients.

For this to work correctly, check should not be used in source/destination
pipeline block.

In particular, this means:

- No logging is done for rejected messages.
- No action is taken if `quarantine_threshold` is hit, only `reject_threshold`
  applies.
- `defer_sender_reject` from SMTP configuration takes no effect.
- MAIL FROM is not checked, even if specified.

If you often get hit by spam attacks, it is recommended to enable this
setting to save server resources.

---

### quarantine_threshold _integer_
Default: `1`

DNSBL score needed (equals-or-higher) to quarantine the message.

---

### reject_threshold _integer_
Default: `9999`

DNSBL score needed (equals-or-higher) to reject the message.

## List configuration

```
dnsbl.example.org dnsbl.example.com {
    client_ipv4 yes
    client_ipv6 no
    ehlo no
    mailfrom no
    responses 127.0.0.1/24
	score 1
}
```

Directive name and arguments specify the actual DNS zone to query when checking
the list. Using multiple arguments is equivalent to specifying the same
configuration separately for each list.

### client_ipv4 _boolean_
Default: `yes`

Whether to check address of the IPv4 clients against the list.

---

### client_ipv6 _boolean_
Default: `yes`

Whether to check address of the IPv6 clients against the list.

---

### ehlo _boolean_
Default: `no`

Whether to check hostname specified n the HELO/EHLO command
against the list.

This works correctly only with domain-based DNSBLs.

---

### mailfrom _boolean_
Default: `no`

Whether to check domain part of the MAIL FROM address against the list.

This works correctly only with domain-based DNSBLs.

---

### responses _cidr_ | _ip..._
Default: `127.0.0.1/24`

IP networks (in CIDR notation) or addresses to permit in list lookup results.
Addresses not matching any entry in this directives will be ignored.

---

### score _integer_
Default: `1`

Score value to add for the message if it is listed.

If sum of list scores is equals or higher than `quarantine_threshold`, the
message will be quarantined.

If sum of list scores is equals or higher than `rejected_threshold`, the message
will be rejected.

It is possible to specify a negative value to make list act like a whitelist
and override results of other blocklists.

**Note:** When using `response` blocks (see below), the score from matching response
rules is used instead of this flat score value.

---

### response _ip..._

Defines per-response-code rules for scoring and custom messages. This is useful
for combined DNSBLs like Spamhaus ZEN that return different codes for different
listing types.

This works for both IP-based lookups (client_ipv4, client_ipv6) and domain-based
lookups (ehlo, mailfrom).

Each `response` block takes one or more IP addresses or CIDR ranges as arguments
and contains the following directives:

#### score _integer_
**Required**

Score to add when this response code is returned. If multiple response codes
are returned by the DNSBL, and they match different rules, the scores from
all matched rules are summed together. Each rule is counted only once, even
if multiple returned IPs match networks within that rule.

#### message _string_
**Optional**

Custom rejection or quarantine message to include when this response code
matches. This message is shown to the client or logged when the threshold
is reached.

**Example:**

```
zen.spamhaus.org {
    client_ipv4 yes
    
    # High severity - known spam sources
    response 127.0.0.2 127.0.0.3 {
        score 10
        message "Listed in Spamhaus SBL"
    }
    
    # Lower severity - dynamic IPs
    response 127.0.0.10 127.0.0.11 {
        score 5
        message "Listed in Spamhaus PBL"
    }
}
```

**Scoring behavior:**
- If DNSBL returns `127.0.0.2` only → Score: 10 (matches first rule)
- If DNSBL returns `127.0.0.11` only → Score: 5 (matches second rule)
- If DNSBL returns both `127.0.0.2` and `127.0.0.11` → Score: 15 (both rules match, scores sum)
- If DNSBL returns both `127.0.0.2` and `127.0.0.3` → Score: 10 (same rule matches, counted once)

**Backwards compatibility:** When `response` blocks are not used, the legacy
`responses` and `score` directives work as before.


================================================
FILE: docs/reference/checks/milter.md
================================================
# Milter client

The 'milter' implements subset of Sendmail's milter protocol that can be used
to integrate external software with maddy.
maddy implements version 6 of the protocol, older versions are
not supported.

Notable limitations of protocol implementation in maddy include:
1. Changes of envelope sender address are not supported
2. Removal and addition of envelope recipients is not supported
3. Removal and replacement of header fields is not supported
4. Headers fields can be inserted only on top
5. Milter does not receive some "macros" provided by sendmail.

Restrictions 1 and 2 are inherent to the maddy checks interface and cannot be
removed without major changes to it. Restrictions 3, 4 and 5 are temporary due to
incomplete implementation.

```
check.milter {
	endpoint <endpoint>
	fail_open false
}

milter <endpoint>
```

## Arguments

When defined inline, the first argument specifies endpoint to access milter
via. See below.

## Configuration directives

### endpoint _scheme://path_
Default: not set

Specifies milter protocol endpoint to use.
The endpoit is specified in standard URL-like format:
`tcp://127.0.0.1:6669` or `unix:///var/lib/milter/filter.sock`

---

### fail_open _boolean_
Default: `false`

Toggles behavior on milter I/O errors. If false ("fail closed") - message is
rejected with temporary error code. If true ("fail open") - check is skipped.



================================================
FILE: docs/reference/checks/misc.md
================================================
# Misc checks

## Configuration directives

Following directives are defined for all modules listed below.

### fail_action `ignore` | `reject` | `quarantine`
Default: `quarantine`

Action to take when check fails. See [Check actions](../actions/) for details.

---

### debug _boolean_
Default: global directive value

Log both successful and unsuccessful check executions instead of just
unsuccessful.

---

### require_mx_record

Check that domain in MAIL FROM command does have a MX record and none of them
are "null" (contain a single dot as the host).

By default, quarantines messages coming from servers missing MX records,
use `fail_action` directive to change that.

---

### require_matching_rdns

Check that source server IP does have a PTR record point to the domain
specified in EHLO/HELO command.

By default, quarantines messages coming from servers with mismatched or missing
PTR record, use `fail_action` directive to change that.

---

### require_tls

Check that the source server is connected via TLS; either directly, or by using
the STARTTLS command.

By default, rejects messages coming from unencrypted servers. Use the
`fail_action` directive to change that.

================================================
FILE: docs/reference/checks/rspamd.md
================================================
# rspamd

The 'rspamd' module implements message filtering by contacting the rspamd
server via HTTP API.

```
check.rspamd {
	tls_client {
Download .txt
gitextract_bng2bnky/

├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github/
│   ├── CODE_OF_CONDUCT.md
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug-report.md
│   │   ├── config.yml
│   │   └── feature-request.md
│   ├── SECURITY.md
│   ├── releases.md
│   └── workflows/
│       ├── release.yml
│       └── test.yml
├── .gitignore
├── .golangci.yml
├── .mkdocs.yml
├── .version
├── AGENTS.md
├── COPYING
├── Dockerfile
├── HACKING.md
├── README.md
├── build.sh
├── cmd/
│   ├── README.md
│   ├── maddy/
│   │   └── main.go
│   ├── maddy-pam-helper/
│   │   ├── README.md
│   │   ├── maddy.conf
│   │   ├── main.c
│   │   ├── main.go
│   │   ├── pam.c
│   │   └── pam.h
│   └── maddy-shadow-helper/
│       ├── README.md
│       └── main.go
├── config.go
├── contrib/
│   ├── README.md
│   └── kubernetes/
│       └── chart/
│           ├── .helmignore
│           ├── Chart.yaml
│           ├── README.md
│           ├── files/
│           │   ├── aliases
│           │   └── maddy.conf
│           ├── templates/
│           │   ├── NOTES.txt
│           │   ├── _helpers.tpl
│           │   ├── configmap.yaml
│           │   ├── deployment.yaml
│           │   ├── pvc.yaml
│           │   ├── service.yaml
│           │   ├── serviceaccount.yaml
│           │   └── tests/
│           │       └── test-connection.yaml
│           └── values.yaml
├── directories.go
├── directories_docker.go
├── dist/
│   ├── README.md
│   ├── apparmor/
│   │   └── dev.foxcpp.maddy
│   ├── fail2ban/
│   │   ├── filter.d/
│   │   │   ├── maddy-auth.conf
│   │   │   └── maddy-dictonary-attack.conf
│   │   └── jail.d/
│   │       ├── maddy-auth.conf
│   │       └── maddy-dictonary-attack.conf
│   ├── install.sh
│   ├── logrotate.d/
│   │   └── maddy
│   ├── systemd/
│   │   ├── maddy.service
│   │   └── maddy@.service
│   └── vim/
│       ├── ftdetect/
│       │   └── maddy-conf.vim
│       ├── ftplugin/
│       │   └── maddy-conf.vim
│       └── syntax/
│           └── maddy-conf.vim
├── docs/
│   ├── docker.md
│   ├── faq.md
│   ├── index.md
│   ├── internals/
│   │   ├── quirks.md
│   │   ├── specifications.md
│   │   ├── sqlite.md
│   │   └── unicode.md
│   ├── man/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── maddy.1.scd
│   │   └── prepare_md.py
│   ├── multiple-domains.md
│   ├── reference/
│   │   ├── auth/
│   │   │   ├── dovecot_sasl.md
│   │   │   ├── external.md
│   │   │   ├── ldap.md
│   │   │   ├── netauth.md
│   │   │   ├── pam.md
│   │   │   ├── pass_table.md
│   │   │   ├── plain_separate.md
│   │   │   └── shadow.md
│   │   ├── blob/
│   │   │   ├── fs.md
│   │   │   └── s3.md
│   │   ├── checks/
│   │   │   ├── actions.md
│   │   │   ├── authorize_sender.md
│   │   │   ├── command.md
│   │   │   ├── dkim.md
│   │   │   ├── dnsbl.md
│   │   │   ├── milter.md
│   │   │   ├── misc.md
│   │   │   ├── rspamd.md
│   │   │   └── spf.md
│   │   ├── config-syntax.md
│   │   ├── endpoints/
│   │   │   ├── imap.md
│   │   │   ├── openmetrics.md
│   │   │   └── smtp.md
│   │   ├── global-config.md
│   │   ├── modifiers/
│   │   │   ├── dkim.md
│   │   │   └── envelope.md
│   │   ├── modules.md
│   │   ├── smtp-pipeline.md
│   │   ├── storage/
│   │   │   ├── imap-filters.md
│   │   │   └── imapsql.md
│   │   ├── table/
│   │   │   ├── auth.md
│   │   │   ├── chain.md
│   │   │   ├── email_localpart.md
│   │   │   ├── email_with_domain.md
│   │   │   ├── file.md
│   │   │   ├── regexp.md
│   │   │   ├── sql_query.md
│   │   │   └── static.md
│   │   ├── targets/
│   │   │   ├── queue.md
│   │   │   ├── remote.md
│   │   │   └── smtp.md
│   │   ├── tls-acme.md
│   │   └── tls.md
│   ├── seclevels.md
│   ├── third-party/
│   │   ├── dovecot.md
│   │   ├── mailman3.md
│   │   ├── rspamd.md
│   │   └── smtp-servers.md
│   ├── tutorials/
│   │   ├── alias-to-remote.md
│   │   ├── building-from-source.md
│   │   ├── pam.md
│   │   └── setting-up.md
│   └── upgrading.md
├── framework/
│   ├── address/
│   │   ├── doc.go
│   │   ├── norm.go
│   │   ├── norm_test.go
│   │   ├── rfc6531.go
│   │   ├── rfc6531_test.go
│   │   ├── split.go
│   │   ├── split_test.go
│   │   ├── validation.go
│   │   └── validation_test.go
│   ├── buffer/
│   │   ├── buffer.go
│   │   ├── bytesreader.go
│   │   ├── file.go
│   │   └── memory.go
│   ├── cfgparser/
│   │   ├── env.go
│   │   ├── imports.go
│   │   ├── parse.go
│   │   └── parse_test.go
│   ├── config/
│   │   ├── config.go
│   │   ├── directories.go
│   │   ├── endpoint.go
│   │   ├── endpoint_test.go
│   │   ├── lexer/
│   │   │   ├── LICENSE.APACHE
│   │   │   ├── README.md
│   │   │   ├── dispenser.go
│   │   │   ├── dispenser_test.go
│   │   │   ├── lexer.go
│   │   │   ├── lexer_test.go
│   │   │   └── parse.go
│   │   ├── map.go
│   │   ├── map_test.go
│   │   ├── module/
│   │   │   ├── check_action.go
│   │   │   ├── interfaces.go
│   │   │   └── modconfig.go
│   │   └── tls/
│   │       ├── client.go
│   │       ├── general.go
│   │       └── server.go
│   ├── container/
│   │   ├── container.go
│   │   ├── lifetime.go
│   │   └── registry.go
│   ├── dns/
│   │   ├── debugflags.go
│   │   ├── dnssec.go
│   │   ├── dnssec_test.go
│   │   ├── idna.go
│   │   ├── norm.go
│   │   ├── override.go
│   │   └── resolver.go
│   ├── exterrors/
│   │   ├── dns.go
│   │   ├── exterrors.go
│   │   ├── fields.go
│   │   ├── smtp.go
│   │   └── temporary.go
│   ├── future/
│   │   ├── future.go
│   │   └── future_test.go
│   ├── hooks/
│   │   └── hooks.go
│   ├── log/
│   │   ├── log.go
│   │   ├── orderedjson.go
│   │   ├── output.go
│   │   ├── syslog.go
│   │   ├── syslog_stub.go
│   │   ├── writer.go
│   │   └── zap.go
│   ├── logparser/
│   │   ├── parse.go
│   │   └── parse_test.go
│   ├── module/
│   │   ├── auth.go
│   │   ├── blob_store.go
│   │   ├── check.go
│   │   ├── delivery_target.go
│   │   ├── imap_filter.go
│   │   ├── modifier.go
│   │   ├── module.go
│   │   ├── module_specific_data.go
│   │   ├── modules/
│   │   │   ├── dummy.go
│   │   │   └── modules.go
│   │   ├── msgmetadata.go
│   │   ├── mxauth.go
│   │   ├── partial_delivery.go
│   │   ├── storage.go
│   │   ├── table.go
│   │   └── tls_loader.go
│   └── resource/
│       ├── netresource/
│       │   ├── dup.go
│       │   ├── fd.go
│       │   ├── listen.go
│       │   └── tracker.go
│       ├── resource.go
│       ├── singleton.go
│       └── tracker.go
├── go.mod
├── go.sum
├── internal/
│   ├── README.md
│   ├── auth/
│   │   ├── auth.go
│   │   ├── auth_test.go
│   │   ├── dovecot_sasl/
│   │   │   └── dovecot_sasl.go
│   │   ├── external/
│   │   │   ├── externalauth.go
│   │   │   └── helperauth.go
│   │   ├── ldap/
│   │   │   └── ldap.go
│   │   ├── netauth/
│   │   │   └── netauth.go
│   │   ├── pam/
│   │   │   ├── module.go
│   │   │   ├── pam.c
│   │   │   ├── pam.go
│   │   │   ├── pam.h
│   │   │   └── pam_stub.go
│   │   ├── pass_table/
│   │   │   ├── hash.go
│   │   │   ├── table.go
│   │   │   └── table_test.go
│   │   ├── plain_separate/
│   │   │   ├── plain_separate.go
│   │   │   └── plain_separate_test.go
│   │   ├── sasl.go
│   │   ├── sasl_test.go
│   │   ├── sasllogin/
│   │   │   └── sasllogin.go
│   │   └── shadow/
│   │       ├── module.go
│   │       ├── read.go
│   │       ├── shadow.go
│   │       └── verify.go
│   ├── authz/
│   │   ├── lookup.go
│   │   └── normalization.go
│   ├── check/
│   │   ├── authorize_sender/
│   │   │   └── authorize_sender.go
│   │   ├── command/
│   │   │   └── command.go
│   │   ├── dkim/
│   │   │   ├── dkim.go
│   │   │   └── dkim_test.go
│   │   ├── dns/
│   │   │   ├── dns.go
│   │   │   └── dns_test.go
│   │   ├── dnsbl/
│   │   │   ├── common.go
│   │   │   ├── common_test.go
│   │   │   ├── dnsbl.go
│   │   │   └── dnsbl_test.go
│   │   ├── milter/
│   │   │   ├── milter.go
│   │   │   └── milter_test.go
│   │   ├── requiretls/
│   │   │   └── requiretls.go
│   │   ├── rspamd/
│   │   │   └── rspamd.go
│   │   ├── skeleton.go
│   │   ├── spf/
│   │   │   └── spf.go
│   │   └── stateless_check.go
│   ├── cli/
│   │   ├── app.go
│   │   ├── clitools/
│   │   │   ├── clitools.go
│   │   │   ├── termios.go
│   │   │   └── termios_stub.go
│   │   ├── ctl/
│   │   │   ├── appendlimit.go
│   │   │   ├── hash.go
│   │   │   ├── imap.go
│   │   │   ├── imapacct.go
│   │   │   ├── moduleinit.go
│   │   │   └── users.go
│   │   └── extflag.go
│   ├── dmarc/
│   │   ├── dmarc.go
│   │   ├── evaluate.go
│   │   ├── evaluate_test.go
│   │   ├── verifier.go
│   │   └── verifier_test.go
│   ├── dsn/
│   │   └── dsn.go
│   ├── endpoint/
│   │   ├── dovecot_sasld/
│   │   │   ├── dovecot_sasl.go
│   │   │   └── mech_info.go
│   │   ├── imap/
│   │   │   └── imap.go
│   │   ├── openmetrics/
│   │   │   └── om.go
│   │   └── smtp/
│   │       ├── date.go
│   │       ├── metrics.go
│   │       ├── session.go
│   │       ├── smtp.go
│   │       ├── smtp_test.go
│   │       ├── smtputf8_test.go
│   │       ├── submission.go
│   │       └── submission_test.go
│   ├── imap_filter/
│   │   ├── command/
│   │   │   └── command.go
│   │   └── group.go
│   ├── libdns/
│   │   ├── acmedns.go
│   │   ├── alidns.go
│   │   ├── cloudflare.go
│   │   ├── digitalocean.go
│   │   ├── gandi.go
│   │   ├── gcore.go
│   │   ├── googleclouddns.go
│   │   ├── hetzner.go
│   │   ├── leaseweb.go
│   │   ├── metaname.go
│   │   ├── namecheap.go
│   │   ├── namedotcom.go
│   │   ├── provider_module.go
│   │   ├── rfc2136.go
│   │   ├── route53.go
│   │   └── vultr.go
│   ├── limits/
│   │   ├── limiters/
│   │   │   ├── bucket.go
│   │   │   ├── concurrency.go
│   │   │   ├── limiters.go
│   │   │   ├── multilimit.go
│   │   │   └── rate.go
│   │   └── limits.go
│   ├── modify/
│   │   ├── dkim/
│   │   │   ├── dkim.go
│   │   │   ├── dkim_test.go
│   │   │   ├── keys.go
│   │   │   └── keys_test.go
│   │   ├── group.go
│   │   ├── replace_addr.go
│   │   └── replace_addr_test.go
│   ├── msgpipeline/
│   │   ├── bench_test.go
│   │   ├── bodynonatomic_test.go
│   │   ├── check_group.go
│   │   ├── check_runner.go
│   │   ├── check_test.go
│   │   ├── config.go
│   │   ├── config_test.go
│   │   ├── dmarc_test.go
│   │   ├── metrics.go
│   │   ├── modifier_test.go
│   │   ├── module.go
│   │   ├── msgpipeline.go
│   │   ├── msgpipeline_test.go
│   │   ├── objname.go
│   │   └── regress_test.go
│   ├── proxy_protocol/
│   │   └── proxy_protocol.go
│   ├── smtpconn/
│   │   ├── pool/
│   │   │   └── pool.go
│   │   ├── smtpconn.go
│   │   ├── smtpconn_test.go
│   │   └── smtputf8_test.go
│   ├── sqlite/
│   │   ├── is.go
│   │   ├── modernc_sqlite3.go
│   │   ├── no_sqlite3.go
│   │   └── sqlite3.go
│   ├── storage/
│   │   ├── blob/
│   │   │   ├── fs/
│   │   │   │   ├── fs.go
│   │   │   │   └── fs_test.go
│   │   │   ├── s3/
│   │   │   │   ├── s3.go
│   │   │   │   └── s3_test.go
│   │   │   ├── test_blob.go
│   │   │   └── test_blob_nosqlite.go
│   │   └── imapsql/
│   │       ├── bench_test.go
│   │       ├── delivery.go
│   │       ├── external_blob_store.go
│   │       ├── imapsql.go
│   │       └── maddyctl.go
│   ├── table/
│   │   ├── chain.go
│   │   ├── email_localpart.go
│   │   ├── email_with_domain.go
│   │   ├── file.go
│   │   ├── file_test.go
│   │   ├── identity.go
│   │   ├── regexp.go
│   │   ├── sql_query.go
│   │   ├── sql_query_test.go
│   │   ├── sql_table.go
│   │   └── static.go
│   ├── target/
│   │   ├── delivery.go
│   │   ├── queue/
│   │   │   ├── metrics.go
│   │   │   ├── queue.go
│   │   │   ├── queue_test.go
│   │   │   ├── timewheel.go
│   │   │   └── timewheel_test.go
│   │   ├── received.go
│   │   ├── remote/
│   │   │   ├── connect.go
│   │   │   ├── dane.go
│   │   │   ├── dane_delivery_test.go
│   │   │   ├── dane_test.go
│   │   │   ├── debugflags.go
│   │   │   ├── metrics.go
│   │   │   ├── mxauth_test.go
│   │   │   ├── policy_group.go
│   │   │   ├── remote.go
│   │   │   ├── remote_test.go
│   │   │   └── security.go
│   │   ├── skeleton.go
│   │   └── smtp/
│   │       ├── sasl.go
│   │       ├── sasl_test.go
│   │       ├── smtp_downstream.go
│   │       ├── smtp_downstream_test.go
│   │       └── smtputf8_test.go
│   ├── testutils/
│   │   ├── bench_delivery.go
│   │   ├── buffer.go
│   │   ├── check.go
│   │   ├── filesystem.go
│   │   ├── logger.go
│   │   ├── modifier.go
│   │   ├── multitable.go
│   │   ├── smtp_server.go
│   │   ├── table.go
│   │   └── target.go
│   ├── tls/
│   │   ├── acme/
│   │   │   └── acme.go
│   │   ├── file.go
│   │   └── self_signed.go
│   └── updatepipe/
│       ├── backend.go
│       ├── pubsub/
│       │   ├── pq.go
│       │   └── pubsub.go
│       ├── pubsub_pipe.go
│       ├── serialize.go
│       ├── unix_pipe.go
│       └── update_pipe.go
├── maddy.conf
├── maddy.conf.docker
├── maddy.go
├── maddy_debug.go
├── signal.go
├── signal_nonposix.go
├── systemd.go
├── systemd_nonlinux.go
└── tests/
    ├── README.md
    ├── basic_test.go
    ├── build_cover.sh
    ├── conn.go
    ├── cover_test.go
    ├── dovecot_sasl_test.go
    ├── dovecot_sasld_test.go
    ├── ghsa_5835_4gvc_32pc_test.go
    ├── gocovcat.go
    ├── golangci-noisy.yml
    ├── imap_test.go
    ├── imapsql_test.go
    ├── issue327_test.go
    ├── limits_test.go
    ├── lmtp_test.go
    ├── modules_test.go
    ├── mta_test.go
    ├── multiple_domains_test.go
    ├── reload_non_unix.go
    ├── reload_test.go
    ├── reload_unix.go
    ├── replace_addr_test.go
    ├── run.sh
    ├── smtp_autobuffer_test.go
    ├── smtp_test.go
    ├── stress_test.go
    ├── t.go
    └── testdata/
        ├── check_command.sh
        ├── testing+addHeader@maddy.test.hdr
        └── testing+reject@maddy.test.exit
Download .txt
SYMBOL INDEX (2069 symbols across 310 files)

FILE: cmd/maddy-pam-helper/main.c
  function run (line 12) | int run(void) {
  function main (line 48) | int main() {

FILE: cmd/maddy-pam-helper/main.go
  function main (line 35) | func main() {

FILE: cmd/maddy-pam-helper/pam.c
  function conv_func (line 26) | static int conv_func(int num_msg, const struct pam_message **msg, struct...
  function run_pam_auth (line 47) | struct error_obj run_pam_auth(const char *username, char *password) {

FILE: cmd/maddy-pam-helper/pam.h
  type error_obj (line 21) | struct error_obj {
  type error_obj (line 27) | struct error_obj

FILE: cmd/maddy-shadow-helper/main.go
  function main (line 30) | func main() {

FILE: cmd/maddy/main.go
  function main (line 27) | func main() {

FILE: config.go
  type logOut (line 38) | type logOut struct
  function logOutput (line 43) | func logOutput(_ *config.Map, node config.Node) (interface{}, error) {
  function LogOutputOption (line 54) | func LogOutputOption(args []string) (log.Output, error) {
  function defaultLogOutput (line 100) | func defaultLogOutput() (interface{}, error) {
  function reinitLogging (line 104) | func reinitLogging() {

FILE: docs/man/prepare_md.py
  function prepare (line 18) | def prepare(r, w):

FILE: framework/address/norm.go
  function ForLookup (line 38) | func ForLookup(addr string) (string, error) {
  function CleanDomain (line 70) | func CleanDomain(addr string) (string, error) {
  function Equal (line 105) | func Equal(addr1, addr2 string) bool {
  function IsASCII (line 117) | func IsASCII(s string) bool {
  function FQDNDomain (line 126) | func FQDNDomain(addr string) string {
  function PRECISFold (line 135) | func PRECISFold(addr string) (string, error) {
  function PRECIS (line 141) | func PRECIS(addr string) (string, error) {
  function precisEmail (line 145) | func precisEmail(addr string, profile *precis.Profile) (string, error) {

FILE: framework/address/norm_test.go
  function addrFuncTest (line 25) | func addrFuncTest(t *testing.T, f func(string) (string, error)) func(in,...
  function TestForLookup (line 41) | func TestForLookup(t *testing.T) {
  function TestCleanDomain (line 52) | func TestCleanDomain(t *testing.T) {
  function TestEqual (line 64) | func TestEqual(t *testing.T) {
  function TestIsASCII (line 81) | func TestIsASCII(t *testing.T) {

FILE: framework/address/rfc6531.go
  function ToASCII (line 32) | func ToASCII(addr string) (string, error) {
  function ToUnicode (line 57) | func ToUnicode(addr string) (string, error) {
  function SelectIDNA (line 80) | func SelectIDNA(ulabel bool, addr string) (string, error) {

FILE: framework/address/rfc6531_test.go
  function TestToASCII (line 26) | func TestToASCII(t *testing.T) {
  function TestToUnicode (line 35) | func TestToUnicode(t *testing.T) {

FILE: framework/address/split.go
  function Split (line 36) | func Split(addr string) (mailbox, domain string, err error) {
  function UnquoteMbox (line 58) | func UnquoteMbox(mbox string) (string, error) {
  function QuoteMbox (line 113) | func QuoteMbox(mbox string) string {

FILE: framework/address/split_test.go
  function TestSplit (line 25) | func TestSplit(t *testing.T) {
  function TestUnquoteMbox (line 63) | func TestUnquoteMbox(t *testing.T) {
  function TestQuoteMbox (line 94) | func TestQuoteMbox(t *testing.T) {

FILE: framework/address/validation.go
  function Valid (line 34) | func Valid(addr string) bool {
  function ValidMailboxName (line 68) | func ValidMailboxName(mbox string) bool {
  function ValidDomain (line 113) | func ValidDomain(domain string) bool {

FILE: framework/address/validation_test.go
  function TestValidMailboxName (line 10) | func TestValidMailboxName(t *testing.T) {
  function TestValidDomain (line 16) | func TestValidDomain(t *testing.T) {

FILE: framework/buffer/buffer.go
  type Buffer (line 40) | type Buffer interface

FILE: framework/buffer/bytesreader.go
  type BytesReader (line 31) | type BytesReader struct
    method Bytes (line 38) | func (br BytesReader) Bytes() []byte {
    method Copy (line 44) | func (br BytesReader) Copy() BytesReader {
    method Close (line 50) | func (br BytesReader) Close() error {
  function NewBytesReader (line 54) | func NewBytesReader(b []byte) BytesReader {

FILE: framework/buffer/file.go
  type FileBuffer (line 31) | type FileBuffer struct
    method Open (line 40) | func (fb FileBuffer) Open() (io.ReadCloser, error) {
    method Len (line 44) | func (fb FileBuffer) Len() int {
    method Remove (line 59) | func (fb FileBuffer) Remove() error {
  function BufferInFile (line 65) | func BufferInFile(r io.Reader, dir string) (Buffer, error) {

FILE: framework/buffer/memory.go
  type MemoryBuffer (line 26) | type MemoryBuffer struct
    method Open (line 30) | func (mb MemoryBuffer) Open() (io.ReadCloser, error) {
    method Len (line 34) | func (mb MemoryBuffer) Len() int {
    method Remove (line 38) | func (mb MemoryBuffer) Remove() error {
  function BufferInMemory (line 44) | func BufferInMemory(r io.Reader) (Buffer, error) {

FILE: framework/cfgparser/env.go
  function expandEnvironment (line 27) | func expandEnvironment(nodes []Node) []Node {
  function removeUnexpandedEnvvars (line 51) | func removeUnexpandedEnvvars(s string) string {
  function buildEnvReplacer (line 56) | func buildEnvReplacer() *strings.Replacer {

FILE: framework/cfgparser/imports.go
  method expandImports (line 28) | func (ctx *parseContext) expandImports(node Node, expansionDepth int) (N...
  method resolveImport (line 77) | func (ctx *parseContext) resolveImport(node Node, name string, expansion...
  method expandMacros (line 114) | func (ctx *parseContext) expandMacros(node *Node) error {
  method expandSingleValueMacro (line 158) | func (ctx *parseContext) expandSingleValueMacro(arg string) (string, err...

FILE: framework/cfgparser/parse.go
  type Node (line 38) | type Node struct
  type parseContext (line 65) | type parseContext struct
    method readNode (line 106) | func (ctx *parseContext) readNode() (Node, error) {
    method isSnippet (line 184) | func (ctx *parseContext) isSnippet(name string) (bool, string) {
    method parseAsMacro (line 191) | func (ctx *parseContext) parseAsMacro(node *Node) (macroName string, a...
    method readNodes (line 219) | func (ctx *parseContext) readNodes() ([]Node, error) {
  function validateNodeName (line 74) | func validateNodeName(s string) error {
  function NodeErr (line 177) | func NodeErr(node Node, f string, args ...interface{}) error {
  function readTree (line 351) | func readTree(r io.Reader, location string, expansionDepth int) (nodes [...
  function Read (line 387) | func Read(r io.Reader, location string) (nodes []Node, err error) {

FILE: framework/cfgparser/parse_test.go
  function printTree (line 576) | func printTree(t *testing.T, root Node, indent int) {
  function TestRead (line 583) | func TestRead(t *testing.T) {

FILE: framework/config/config.go
  function NodeErr (line 31) | func NodeErr(node Node, f string, args ...interface{}) error {

FILE: framework/config/endpoint.go
  type Endpoint (line 33) | type Endpoint struct
    method String (line 38) | func (e Endpoint) String() string {
    method Network (line 70) | func (e Endpoint) Network() string {
    method Address (line 77) | func (e Endpoint) Address() string {
    method IsTLS (line 84) | func (e Endpoint) IsTLS() bool {
  function ParseEndpoint (line 90) | func ParseEndpoint(str string) (Endpoint, error) {

FILE: framework/config/endpoint_test.go
  function TestStandardizeAddress (line 26) | func TestStandardizeAddress(t *testing.T) {

FILE: framework/config/lexer/dispenser.go
  type Dispenser (line 45) | type Dispenser struct
    method Next (line 74) | func (d *Dispenser) Next() bool {
    method NextArg (line 86) | func (d *Dispenser) NextArg() bool {
    method NextLine (line 107) | func (d *Dispenser) NextLine() bool {
    method NextBlock (line 130) | func (d *Dispenser) NextBlock() bool {
    method Val (line 157) | func (d *Dispenser) Val() string {
    method Line (line 166) | func (d *Dispenser) Line() int {
    method File (line 175) | func (d *Dispenser) File() string {
    method Args (line 191) | func (d *Dispenser) Args(targets ...*string) bool {
    method RemainingArgs (line 207) | func (d *Dispenser) RemainingArgs() []string {
    method ArgErr (line 225) | func (d *Dispenser) ArgErr() error {
    method SyntaxErr (line 234) | func (d *Dispenser) SyntaxErr(expected string) error {
    method EOFErr (line 241) | func (d *Dispenser) EOFErr() error {
    method Err (line 246) | func (d *Dispenser) Err(msg string) error {
    method Errf (line 252) | func (d *Dispenser) Errf(format string, args ...interface{}) error {
    method numLineBreaks (line 259) | func (d *Dispenser) numLineBreaks(tknIdx int) int {
  function NewDispenser (line 53) | func NewDispenser(filename string, input io.Reader) Dispenser {
  function NewDispenserTokens (line 63) | func NewDispenserTokens(filename string, tokens []Token) Dispenser {

FILE: framework/config/lexer/dispenser_test.go
  function TestDispenser_Val_Next (line 41) | func TestDispenser_Val_Next(t *testing.T) {
  function TestDispenser_NextArg (line 80) | func TestDispenser_NextArg(t *testing.T) {
  function TestDispenser_NextLine (line 127) | func TestDispenser_NextLine(t *testing.T) {
  function TestDispenser_NextBlock (line 157) | func TestDispenser_NextBlock(t *testing.T) {
  function TestDispenser_Args (line 188) | func TestDispenser_Args(t *testing.T) {
  function TestDispenser_RemainingArgs (line 256) | func TestDispenser_RemainingArgs(t *testing.T) {
  function TestDispenser_ArgErr_Err (line 294) | func TestDispenser_ArgErr_Err(t *testing.T) {

FILE: framework/config/lexer/lexer.go
  type lexer (line 46) | type lexer struct
    method load (line 64) | func (l *lexer) load(input io.Reader) error {
    method err (line 83) | func (l *lexer) err() error {
    method next (line 100) | func (l *lexer) next() bool {
  type Token (line 55) | type Token struct

FILE: framework/config/lexer/lexer_test.go
  type lexerTestCase (line 41) | type lexerTestCase struct
  function TestLexer (line 46) | func TestLexer(t *testing.T) {
  function tokenize (line 178) | func tokenize(input string) (tokens []Token) {
  function lexerCompare (line 189) | func lexerCompare(t *testing.T, n int, expected, actual []Token) {

FILE: framework/config/lexer/parse.go
  function allTokens (line 28) | func allTokens(input io.Reader) ([]Token, error) {

FILE: framework/config/map.go
  type matcher (line 31) | type matcher struct
    method assign (line 42) | func (m *matcher) assign(val interface{}) {
  type Map (line 54) | type Map struct
    method AllowUnknown (line 74) | func (m *Map) AllowUnknown() {
    method EnumList (line 84) | func (m *Map) EnumList(name string, inheritGlobal, required bool, allo...
    method Enum (line 117) | func (m *Map) Enum(name string, inheritGlobal, required bool, allowed ...
    method Duration (line 202) | func (m *Map) Duration(name string, inheritGlobal, required bool, defa...
    method DataSize (line 288) | func (m *Map) DataSize(name string, inheritGlobal, required bool, defa...
    method Bool (line 326) | func (m *Map) Bool(name string, inheritGlobal, defaultVal bool, store ...
    method StringList (line 357) | func (m *Map) StringList(name string, inheritGlobal, required bool, de...
    method String (line 379) | func (m *Map) String(name string, inheritGlobal, required bool, defaul...
    method Int (line 401) | func (m *Map) Int(name string, inheritGlobal, required bool, defaultVa...
    method UInt (line 427) | func (m *Map) UInt(name string, inheritGlobal, required bool, defaultV...
    method Int32 (line 453) | func (m *Map) Int32(name string, inheritGlobal, required bool, default...
    method UInt32 (line 479) | func (m *Map) UInt32(name string, inheritGlobal, required bool, defaul...
    method Int64 (line 505) | func (m *Map) Int64(name string, inheritGlobal, required bool, default...
    method UInt64 (line 531) | func (m *Map) UInt64(name string, inheritGlobal, required bool, defaul...
    method Float (line 557) | func (m *Map) Float(name string, inheritGlobal, required bool, default...
    method Custom (line 595) | func (m *Map) Custom(name string, inheritGlobal, required bool, defaul...
    method Callback (line 631) | func (m *Map) Callback(name string, mapper func(*Map, Node) error) {
    method Process (line 648) | func (m *Map) Process() (unknown []Node, err error) {
    method ProcessWith (line 653) | func (m *Map) ProcessWith(globalCfg map[string]interface{}, block Node...
  function NewMap (line 68) | func NewMap(globals map[string]interface{}, block Node) *Map {
  function EnumMapped (line 139) | func EnumMapped[V any](m *Map, name string, inheritGlobal, required bool...
  function EnumListMapped (line 164) | func EnumListMapped[V any](m *Map, name string, inheritGlobal, required ...
  function ParseDataSize (line 227) | func ParseDataSize(s string) (int, error) {
  function ParseBool (line 309) | func ParseBool(s string) (bool, error) {

FILE: framework/config/map_test.go
  function TestMapProcess (line 25) | func TestMapProcess(t *testing.T) {
  function TestMapProcess_MissingRequired (line 52) | func TestMapProcess_MissingRequired(t *testing.T) {
  function TestMapProcess_InheritGlobal (line 70) | func TestMapProcess_InheritGlobal(t *testing.T) {
  function TestMapProcess_InheritGlobal_MissingRequired (line 92) | func TestMapProcess_InheritGlobal_MissingRequired(t *testing.T) {
  function TestMapProcess_InheritGlobal_Override (line 110) | func TestMapProcess_InheritGlobal_Override(t *testing.T) {
  function TestMapProcess_DefaultValue (line 137) | func TestMapProcess_DefaultValue(t *testing.T) {
  function TestMapProcess_InheritGlobal_DefaultValue (line 161) | func TestMapProcess_InheritGlobal_DefaultValue(t *testing.T) {
  function TestMapProcess_Duplicate (line 196) | func TestMapProcess_Duplicate(t *testing.T) {
  function TestMapProcess_Unexpected (line 223) | func TestMapProcess_Unexpected(t *testing.T) {
  function TestMapInt (line 265) | func TestMapInt(t *testing.T) {
  function TestMapInt_Invalid (line 290) | func TestMapInt_Invalid(t *testing.T) {
  function TestMapFloat (line 311) | func TestMapFloat(t *testing.T) {
  function TestMapFloat_Invalid (line 336) | func TestMapFloat_Invalid(t *testing.T) {
  function TestMapBool (line 357) | func TestMapBool(t *testing.T) {
  function TestParseDataSize (line 401) | func TestParseDataSize(t *testing.T) {
  function TestMap_Callback (line 433) | func TestMap_Callback(t *testing.T) {

FILE: framework/config/module/check_action.go
  type FailAction (line 55) | type FailAction struct
    method Apply (line 102) | func (cfa FailAction) Apply(originalRes module.CheckResult) module.Che...
  function FailActionDirective (line 62) | func FailActionDirective(_ *config.Map, node config.Node) (interface{}, ...
  function ParseActionDirective (line 74) | func ParseActionDirective(args []string) (FailAction, error) {
  function ParseRejectDirective (line 122) | func ParseRejectDirective(args []string) (*exterrors.SMTPError, error) {
  function parseEnhancedCode (line 169) | func parseEnhancedCode(s string) (exterrors.EnhancedCode, error) {

FILE: framework/config/module/interfaces.go
  function MessageCheck (line 26) | func MessageCheck(globals map[string]interface{}, args []string, block c...
  function DeliveryDirective (line 45) | func DeliveryDirective(m *config.Map, node config.Node) (interface{}, er...
  function DeliveryTarget (line 49) | func DeliveryTarget(globals map[string]interface{}, args []string, block...
  function MsgModifier (line 57) | func MsgModifier(globals map[string]interface{}, args []string, block co...
  function IMAPFilter (line 65) | func IMAPFilter(globals map[string]interface{}, args []string, block con...
  function StorageDirective (line 73) | func StorageDirective(m *config.Map, node config.Node) (interface{}, err...
  function Table (line 86) | func Table(cfg *config.Map, name string, inheritGlobal, required bool, d...
  function TableDirective (line 92) | func TableDirective(m *config.Map, node config.Node) (interface{}, error) {

FILE: framework/config/module/modconfig.go
  function createInlineModule (line 43) | func createInlineModule(c *container.C, preferredNamespace, modName stri...
  function configureInlineModule (line 71) | func configureInlineModule(modObj module.Module, args []string, globals ...
  function ModuleFromNode (line 102) | func ModuleFromNode(preferredNamespace string, args []string, inlineCfg ...
  function GroupFromNode (line 153) | func GroupFromNode(defaultModule string, args []string, inlineCfg config...

FILE: framework/config/tls/client.go
  function TLSClientBlock (line 31) | func TLSClientBlock(_ *config.Map, node config.Node) (interface{}, error) {

FILE: framework/config/tls/general.go
  function TLSVersionsDirective (line 73) | func TLSVersionsDirective(_ *config.Map, node config.Node) (interface{},...
  function TLSCiphersDirective (line 100) | func TLSCiphersDirective(_ *config.Map, node config.Node) (interface{}, ...
  function TLSCurvesDirective (line 121) | func TLSCurvesDirective(_ *config.Map, node config.Node) (interface{}, e...

FILE: framework/config/tls/server.go
  type TLSConfig (line 30) | type TLSConfig struct
    method Get (line 35) | func (cfg *TLSConfig) Get() (*tls.Config, error) {
  function TLSDirective (line 54) | func TLSDirective(m *config.Map, node config.Node) (interface{}, error) {
  function readTLSBlock (line 71) | func readTLSBlock(globals map[string]interface{}, blockNode config.Node)...

FILE: framework/container/container.go
  type GlobalConfig (line 25) | type GlobalConfig struct
  type C (line 53) | type C struct
  function New (line 60) | func New() *C {

FILE: framework/container/lifetime.go
  type LifetimeModule (line 30) | type LifetimeModule interface
  type ReloadModule (line 36) | type ReloadModule interface
  type EarlyStopModule (line 43) | type EarlyStopModule interface
  type LifetimeTracker (line 48) | type LifetimeTracker struct
    method Add (line 57) | func (lt *LifetimeTracker) Add(mod LifetimeModule) {
    method StartAll (line 66) | func (lt *LifetimeTracker) StartAll() error {
    method ReloadAll (line 89) | func (lt *LifetimeTracker) ReloadAll() error {
    method EarlyStopAll (line 112) | func (lt *LifetimeTracker) EarlyStopAll() error {
    method StopAll (line 139) | func (lt *LifetimeTracker) StopAll() error {
  function NewLifetime (line 160) | func NewLifetime(log *log.Logger) *LifetimeTracker {

FILE: framework/container/registry.go
  type registryEntry (line 33) | type registryEntry struct
  type Registry (line 38) | type Registry struct
    method Register (line 60) | func (r *Registry) Register(mod module.Module, lazyInit func() error) ...
    method AddAlias (line 78) | func (r *Registry) AddAlias(instanceName string, alias string) error {
    method ensureInitialized (line 98) | func (r *Registry) ensureInitialized(name string, entry *registryEntry...
    method Get (line 118) | func (r *Registry) Get(name string) (module.Module, error) {
    method NotInitialized (line 139) | func (r *Registry) NotInitialized() []module.Module {
  function NewRegistry (line 46) | func NewRegistry(log *log.Logger) *Registry {

FILE: framework/dns/debugflags.go
  function init (line 29) | func init() {

FILE: framework/dns/dnssec.go
  type ExtResolver (line 37) | type ExtResolver struct
    method exchange (line 87) | func (e ExtResolver) exchange(ctx context.Context, msg *dns.Msg) (*dns...
    method AuthLookupAddr (line 113) | func (e ExtResolver) AuthLookupAddr(ctx context.Context, addr string) ...
    method AuthLookupHost (line 142) | func (e ExtResolver) AuthLookupHost(ctx context.Context, host string) ...
    method AuthLookupMX (line 155) | func (e ExtResolver) AuthLookupMX(ctx context.Context, name string) (a...
    method AuthLookupTXT (line 182) | func (e ExtResolver) AuthLookupTXT(ctx context.Context, name string) (...
    method CheckCNAMEAD (line 211) | func (e ExtResolver) CheckCNAMEAD(ctx context.Context, host string) (a...
    method AuthLookupCNAME (line 250) | func (e ExtResolver) AuthLookupCNAME(ctx context.Context, host string)...
    method AuthLookupIPAddr (line 271) | func (e ExtResolver) AuthLookupIPAddr(ctx context.Context, host string...
    method AuthLookupTLSA (line 347) | func (e ExtResolver) AuthLookupTLSA(ctx context.Context, service, netw...
  type RCodeError (line 44) | type RCodeError struct
    method Temporary (line 49) | func (err RCodeError) Temporary() bool {
    method Error (line 53) | func (err RCodeError) Error() string {
  function IsNotFound (line 69) | func IsNotFound(err error) bool {
  function isLoopback (line 79) | func isLoopback(addr string) bool {
  function NewExtResolver (line 376) | func NewExtResolver() (*ExtResolver, error) {

FILE: framework/dns/dnssec_test.go
  type TestSrvAction (line 17) | type TestSrvAction
    method String (line 26) | func (a TestSrvAction) String() string {
  constant TestSrvTimeout (line 20) | TestSrvTimeout TestSrvAction = iota
  constant TestSrvServfail (line 21) | TestSrvServfail
  constant TestSrvNoAddr (line 22) | TestSrvNoAddr
  constant TestSrvOk (line 23) | TestSrvOk
  type IPAddrTestServer (line 41) | type IPAddrTestServer struct
    method Run (line 49) | func (s *IPAddrTestServer) Run() {
    method Close (line 59) | func (s *IPAddrTestServer) Close() error {
    method Addr (line 63) | func (s *IPAddrTestServer) Addr() *net.UDPAddr {
    method ServeDNS (line 67) | func (s *IPAddrTestServer) ServeDNS(w dns.ResponseWriter, m *dns.Msg) {
  function TestExtResolver_AuthLookupIPAddr (line 126) | func TestExtResolver_AuthLookupIPAddr(t *testing.T) {

FILE: framework/dns/idna.go
  function SelectIDNA (line 31) | func SelectIDNA(ulabel bool, domain string) (string, error) {

FILE: framework/dns/norm.go
  function FQDN (line 29) | func FQDN(domain string) string {
  function ForLookup (line 41) | func ForLookup(domain string) (string, error) {
  function Equal (line 62) | func Equal(domain1, domain2 string) bool {

FILE: framework/dns/override.go
  function override (line 35) | func override(server string) { // nolint: unused // used in debugflags.go

FILE: framework/dns/resolver.go
  type Resolver (line 36) | type Resolver interface
  function LookupAddr (line 47) | func LookupAddr(ctx context.Context, r Resolver, ip net.IP) (string, err...
  function DefaultResolver (line 55) | func DefaultResolver() Resolver {

FILE: framework/exterrors/dns.go
  function UnwrapDNSErr (line 25) | func UnwrapDNSErr(err error) (reason string, misc map[string]interface{}) {

FILE: framework/exterrors/fields.go
  type fieldsErr (line 21) | type fieldsErr interface
  type unwrapper (line 25) | type unwrapper interface
  type fieldsWrap (line 29) | type fieldsWrap struct
    method Error (line 34) | func (fw fieldsWrap) Error() string {
    method Unwrap (line 38) | func (fw fieldsWrap) Unwrap() error {
    method Fields (line 42) | func (fw fieldsWrap) Fields() map[string]interface{} {
  function Fields (line 46) | func Fields(err error) map[string]interface{} {
  function WithFields (line 72) | func WithFields(err error, fields map[string]interface{}) error {

FILE: framework/exterrors/smtp.go
  type EnhancedCode (line 27) | type EnhancedCode
    method FormatLog (line 29) | func (ec EnhancedCode) FormatLog() string {
  type SMTPError (line 37) | type SMTPError struct
    method Unwrap (line 86) | func (se *SMTPError) Unwrap() error {
    method Fields (line 90) | func (se *SMTPError) Fields() map[string]interface{} {
    method Temporary (line 113) | func (se *SMTPError) Temporary() bool {
    method Error (line 117) | func (se *SMTPError) Error() string {
  function SMTPCode (line 130) | func SMTPCode(err error, temporaryCode, permanentCode int) int {
  function SMTPEnchCode (line 140) | func SMTPEnchCode(err error, code EnhancedCode) EnhancedCode {

FILE: framework/exterrors/temporary.go
  type TemporaryErr (line 25) | type TemporaryErr interface
  function IsTemporaryOrUnspec (line 33) | func IsTemporaryOrUnspec(err error) bool {
  function IsTemporary (line 43) | func IsTemporary(err error) bool {
  type temporaryErr (line 51) | type temporaryErr struct
    method Unwrap (line 56) | func (t temporaryErr) Unwrap() error {
    method Error (line 60) | func (t temporaryErr) Error() string {
    method Temporary (line 64) | func (t temporaryErr) Temporary() bool {
  function WithTemporary (line 72) | func WithTemporary(err error, temporary bool) error {

FILE: framework/future/future.go
  type Future (line 33) | type Future struct
    method Set (line 48) | func (f *Future) Set(val interface{}, err error) {
    method Get (line 70) | func (f *Future) Get() (interface{}, error) {
    method GetContext (line 78) | func (f *Future) GetContext(ctx context.Context) (interface{}, error) {
  function New (line 42) | func New() *Future {

FILE: framework/future/future_test.go
  function TestFuture_SetBeforeGet (line 28) | func TestFuture_SetBeforeGet(t *testing.T) {
  function TestFuture_Wait (line 42) | func TestFuture_Wait(t *testing.T) {
  function TestFuture_WaitCtx (line 67) | func TestFuture_WaitCtx(t *testing.T) {

FILE: framework/hooks/hooks.go
  type Event (line 23) | type Event
  constant EventShutdown (line 27) | EventShutdown Event = iota
  constant EventReload (line 36) | EventReload
  constant EventLogRotate (line 41) | EventLogRotate
  function hooksToRun (line 49) | func hooksToRun(eventName Event) []func() {
  function RunHooks (line 67) | func RunHooks(eventName Event) {
  function AddHook (line 75) | func AddHook(eventName Event, f func()) {

FILE: framework/log/log.go
  type Logger (line 44) | type Logger struct
    method Zap (line 56) | func (l *Logger) Zap() *zap.Logger {
    method IsDebug (line 61) | func (l *Logger) IsDebug() bool {
    method Debugf (line 65) | func (l *Logger) Debugf(format string, val ...interface{}) {
    method Debugln (line 72) | func (l *Logger) Debugln(val ...interface{}) {
    method Printf (line 79) | func (l *Logger) Printf(format string, val ...interface{}) {
    method Println (line 83) | func (l *Logger) Println(val ...interface{}) {
    method Msg (line 102) | func (l *Logger) Msg(msg string, fields ...interface{}) {
    method Error (line 121) | func (l *Logger) Error(msg string, err error, fields ...interface{}) {
    method DebugMsg (line 142) | func (l *Logger) DebugMsg(kind string, fields ...interface{}) {
    method formatMsg (line 171) | func (l *Logger) formatMsg(msg string, fields map[string]interface{}) ...
    method Write (line 200) | func (l *Logger) Write(s []byte) (int, error) {
    method DebugWriter (line 211) | func (l *Logger) DebugWriter() io.Writer {
    method output (line 217) | func (l *Logger) output() Output {
    method log (line 234) | func (l *Logger) log(debug bool, s string) {
    method Sublogger (line 245) | func (l *Logger) Sublogger(name string) *Logger {
  function fieldsToMap (line 151) | func fieldsToMap(fields []interface{}, out map[string]interface{}) {
  type Formatter (line 193) | type Formatter interface
  function Debugf (line 268) | func Debugf(format string, val ...interface{}) { DefaultLogger.Debugf(fo...
  function Debugln (line 269) | func Debugln(val ...interface{})               { DefaultLogger.Debugln(v...
  function Printf (line 270) | func Printf(format string, val ...interface{}) { DefaultLogger.Printf(fo...
  function Println (line 271) | func Println(val ...interface{})               { DefaultLogger.Println(v...

FILE: framework/log/orderedjson.go
  type module (line 34) | type module interface
  function marshalOrderedJSON (line 39) | func marshalOrderedJSON(output *strings.Builder, m map[string]interface{...

FILE: framework/log/output.go
  type Output (line 25) | type Output interface
  type multiOut (line 30) | type multiOut struct
    method Write (line 34) | func (m multiOut) Write(stamp time.Time, debug bool, msg string) {
    method Close (line 40) | func (m multiOut) Close() error {
  function MultiOutput (line 49) | func MultiOutput(outputs ...Output) Output {
  type funcOut (line 53) | type funcOut struct
    method Write (line 58) | func (f funcOut) Write(stamp time.Time, debug bool, msg string) {
    method Close (line 62) | func (f funcOut) Close() error {
  function FuncOutput (line 66) | func FuncOutput(f func(time.Time, bool, string), close func() error) Out...
  type NopOutput (line 70) | type NopOutput struct
    method Write (line 72) | func (NopOutput) Write(time.Time, bool, string) {}
    method Close (line 74) | func (NopOutput) Close() error { return nil }

FILE: framework/log/syslog.go
  type syslogOut (line 31) | type syslogOut struct
    method Write (line 35) | func (s syslogOut) Write(stamp time.Time, debug bool, msg string) {
    method Close (line 48) | func (s syslogOut) Close() error {
  function SyslogOutput (line 59) | func SyslogOutput() (Output, error) {

FILE: framework/log/syslog_stub.go
  function SyslogOutput (line 35) | func SyslogOutput() (Output, error) {

FILE: framework/log/writer.go
  type wcOutput (line 29) | type wcOutput struct
    method Write (line 34) | func (w wcOutput) Write(stamp time.Time, debug bool, msg string) {
    method Close (line 49) | func (w wcOutput) Close() error {
  function WriteCloserOutput (line 67) | func WriteCloserOutput(wc io.WriteCloser, timestamps bool) Output {
  type nopCloser (line 71) | type nopCloser struct
    method Close (line 75) | func (nc nopCloser) Close() error {
  function WriterOutput (line 93) | func WriterOutput(w io.Writer, timestamps bool) Output {

FILE: framework/log/zap.go
  type zapLogger (line 9) | type zapLogger struct
    method Enabled (line 13) | func (l zapLogger) Enabled(level zapcore.Level) bool {
    method With (line 20) | func (l zapLogger) With(fields []zapcore.Field) zapcore.Core {
    method Check (line 36) | func (l zapLogger) Check(entry zapcore.Entry, ce *zapcore.CheckedEntry...
    method Write (line 43) | func (l zapLogger) Write(entry zapcore.Entry, fields []zapcore.Field) ...
    method Sync (line 55) | func (zapLogger) Sync() error {

FILE: framework/logparser/parse.go
  type Msg (line 31) | type Msg struct
  type MalformedMsg (line 39) | type MalformedMsg struct
    method Error (line 49) | func (m MalformedMsg) Error() string {
  constant ISO8601_UTC (line 46) | ISO8601_UTC = "2006-01-02T15:04:05.000Z"
  function Parse (line 64) | func Parse(line string) (Msg, error) {

FILE: framework/logparser/parse_test.go
  function TestParse (line 27) | func TestParse(t *testing.T) {

FILE: framework/module/auth.go
  type PlainAuth (line 32) | type PlainAuth interface
  type PlainUserDB (line 38) | type PlainUserDB interface

FILE: framework/module/blob_store.go
  type Blob (line 9) | type Blob interface
  constant UnknownBlobSize (line 17) | UnknownBlobSize int64 = -1
  type BlobStore (line 21) | type BlobStore interface

FILE: framework/module/check.go
  type Check (line 34) | type Check interface
  type EarlyCheck (line 62) | type EarlyCheck interface
  type CheckState (line 66) | type CheckState interface
  type CheckResult (line 90) | type CheckResult struct

FILE: framework/module/delivery_target.go
  type DeliveryTarget (line 35) | type DeliveryTarget interface
  type Delivery (line 44) | type Delivery interface

FILE: framework/module/imap_filter.go
  type IMAPFilter (line 31) | type IMAPFilter interface

FILE: framework/module/modifier.go
  type Modifier (line 46) | type Modifier interface
  type ModifierState (line 52) | type ModifierState interface

FILE: framework/module/module.go
  type Module (line 41) | type Module interface

FILE: framework/module/module_specific_data.go
  type ModSpecificData (line 20) | type ModSpecificData struct
    method modKey (line 25) | func (msd *ModSpecificData) modKey(m Module, perInstance bool) string {
    method MarshalJSON (line 36) | func (msd *ModSpecificData) MarshalJSON() ([]byte, error) {
    method UnmarshalJSON (line 42) | func (msd *ModSpecificData) UnmarshalJSON(b []byte) error {
    method Set (line 48) | func (msd *ModSpecificData) Set(m Module, perInstance bool, value inte...
    method Get (line 58) | func (msd *ModSpecificData) Get(m Module, perInstance bool) interface{} {

FILE: framework/module/modules/dummy.go
  type Dummy (line 37) | type Dummy struct
    method AuthPlain (line 39) | func (d *Dummy) AuthPlain(username, _ string) error {
    method Lookup (line 43) | func (d *Dummy) Lookup(_ context.Context, _ string) (string, bool, err...
    method LookupMulti (line 47) | func (d *Dummy) LookupMulti(_ context.Context, _ string) ([]string, er...
    method Name (line 51) | func (d *Dummy) Name() string {
    method InstanceName (line 55) | func (d *Dummy) InstanceName() string {
    method Configure (line 59) | func (d *Dummy) Configure(_ []string, _ *config.Map) error {
    method StartDelivery (line 63) | func (d *Dummy) StartDelivery(ctx context.Context, msgMeta *module.Msg...
  type dummyDelivery (line 67) | type dummyDelivery struct
    method AddRcpt (line 69) | func (dd dummyDelivery) AddRcpt(ctx context.Context, rcptTo string, op...
    method Body (line 73) | func (dd dummyDelivery) Body(ctx context.Context, header textproto.Hea...
    method Abort (line 77) | func (dd dummyDelivery) Abort(ctx context.Context) error {
    method Commit (line 81) | func (dd dummyDelivery) Commit(ctx context.Context) error {
  function NewDummy (line 85) | func NewDummy(_ *container.C, _, instName string) (module.Module, error) {

FILE: framework/module/modules/modules.go
  type FuncNewModule (line 35) | type FuncNewModule
  type FuncNewEndpoint (line 49) | type FuncNewEndpoint
  function Register (line 63) | func Register(name string, factory FuncNewModule) {
  function RegisterDeprecated (line 78) | func RegisterDeprecated(name, newName string, factory FuncNewModule) {
  function Get (line 90) | func Get(name string) FuncNewModule {
  function GetEndpoint (line 100) | func GetEndpoint(name string) FuncNewEndpoint {
  function RegisterEndpoint (line 111) | func RegisterEndpoint(name string, factory FuncNewEndpoint) {
  function init (line 122) | func init() {

FILE: framework/module/msgmetadata.go
  type ConnState (line 34) | type ConnState struct
  type MsgMetadata (line 84) | type MsgMetadata struct
    method DeepCopy (line 146) | func (msgMeta *MsgMetadata) DeepCopy() *MsgMetadata {
  function GenerateMsgID (line 154) | func GenerateMsgID() (string, error) {

FILE: framework/module/mxauth.go
  constant AuthDisabled (line 27) | AuthDisabled     = "off"
  constant AuthMTASTS (line 28) | AuthMTASTS       = "mtasts"
  constant AuthDNSSEC (line 29) | AuthDNSSEC       = "dnssec"
  constant AuthCommonDomain (line 30) | AuthCommonDomain = "common_domain"
  type TLSLevel (line 34) | type TLSLevel
    method String (line 50) | func (l TLSLevel) String() string {
  type MXLevel (line 35) | type MXLevel
    method String (line 62) | func (l MXLevel) String() string {
  constant TLSNone (line 39) | TLSNone TLSLevel = iota
  constant TLSEncrypted (line 40) | TLSEncrypted
  constant TLSAuthenticated (line 41) | TLSAuthenticated
  constant MXNone (line 45) | MXNone MXLevel = iota
  constant MX_MTASTS (line 46) | MX_MTASTS
  constant MX_DNSSEC (line 47) | MX_DNSSEC
  type MXAuthPolicy (line 98) | type MXAuthPolicy interface
  type DeliveryMXAuthPolicy (line 109) | type DeliveryMXAuthPolicy interface

FILE: framework/module/partial_delivery.go
  type StatusCollector (line 31) | type StatusCollector interface
  type PartialDelivery (line 50) | type PartialDelivery interface

FILE: framework/module/storage.go
  type Storage (line 30) | type Storage interface
  type ManageableStorage (line 44) | type ManageableStorage interface

FILE: framework/module/table.go
  type Table (line 28) | type Table interface
  type MultiTable (line 34) | type MultiTable interface
  type MutableTable (line 38) | type MutableTable interface

FILE: framework/module/tls_loader.go
  type TLSLoader (line 39) | type TLSLoader interface

FILE: framework/resource/netresource/dup.go
  function dupTCPListener (line 5) | func dupTCPListener(l *net.TCPListener) (*net.TCPListener, error) {
  function dupUnixListener (line 17) | func dupUnixListener(l *net.UnixListener) (*net.UnixListener, error) {

FILE: framework/resource/netresource/fd.go
  function ListenFD (line 12) | func ListenFD(fd uint) (net.Listener, error) {
  function ListenFDName (line 22) | func ListenFDName(name string) (net.Listener, error) {

FILE: framework/resource/netresource/listen.go
  function CloseUnusedListeners (line 15) | func CloseUnusedListeners() error {
  function CloseAllListeners (line 19) | func CloseAllListeners() error {
  function ResetListenersUsage (line 23) | func ResetListenersUsage() {
  function Listen (line 27) | func Listen(network, addr string) (net.Listener, error) {

FILE: framework/resource/netresource/tracker.go
  type ListenerTracker (line 12) | type ListenerTracker struct
    method Get (line 18) | func (lt *ListenerTracker) Get(network, addr string) (net.Listener, er...
    method ResetUsage (line 64) | func (lt *ListenerTracker) ResetUsage() {
    method CloseUnused (line 69) | func (lt *ListenerTracker) CloseUnused() error {
    method Close (line 79) | func (lt *ListenerTracker) Close() error {
  function NewListenerTracker (line 89) | func NewListenerTracker(log *log.Logger) *ListenerTracker {

FILE: framework/resource/resource.go
  type CheckableResource (line 9) | type CheckableResource interface
  type Container (line 14) | type Container interface

FILE: framework/resource/singleton.go
  type Singleton (line 10) | type Singleton struct
  function NewSingleton (line 16) | func NewSingleton[T Resource](log *log.Logger) *Singleton[T] {
  method GetOpen (line 23) | func (s *Singleton[T]) GetOpen(key string, open func() (T, error)) (T, e...
  method CloseUnused (line 45) | func (s *Singleton[T]) CloseUnused(isUsed func(key string) bool) error {
  method Close (line 63) | func (s *Singleton[T]) Close() error {

FILE: framework/resource/tracker.go
  type Tracker (line 9) | type Tracker struct
  function NewTracker (line 16) | func NewTracker[T Resource](c Container[T]) *Tracker[T] {
  method Close (line 20) | func (t *Tracker[T]) Close() error {
  method MarkAllUnused (line 24) | func (t *Tracker[T]) MarkAllUnused() {
  method GetOpen (line 31) | func (t *Tracker[T]) GetOpen(key string, open func() (T, error)) (T, err...
  method CloseUnused (line 39) | func (t *Tracker[T]) CloseUnused(isUsed func(key string) bool) error {

FILE: internal/auth/auth.go
  function CheckDomainAuth (line 23) | func CheckDomainAuth(username string, perDomain bool, allowedDomains []s...

FILE: internal/auth/auth_test.go
  function TestCheckDomainAuth (line 26) | func TestCheckDomainAuth(t *testing.T) {

FILE: internal/auth/dovecot_sasl/dovecot_sasl.go
  type Auth (line 36) | type Auth struct
    method Name (line 58) | func (a *Auth) Name() string {
    method InstanceName (line 62) | func (a *Auth) InstanceName() string {
    method getConn (line 66) | func (a *Auth) getConn() (*dovecotsasl.Client, error) {
    method returnConn (line 81) | func (a *Auth) returnConn(cl *dovecotsasl.Client) {
    method Configure (line 87) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method AuthPlain (line 139) | func (a *Auth) AuthPlain(username, password string) error {
  constant modName (line 47) | modName = "dovecot_sasl"
  function New (line 49) | func New(c *container.C, _, instName string) (module.Module, error) {
  function init (line 166) | func init() {

FILE: internal/auth/external/externalauth.go
  type ExternalAuth (line 35) | type ExternalAuth struct
    method Name (line 56) | func (ea *ExternalAuth) Name() string {
    method InstanceName (line 60) | func (ea *ExternalAuth) InstanceName() string {
    method Configure (line 64) | func (ea *ExternalAuth) Configure(inlineArgs []string, cfg *config.Map...
    method AuthPlain (line 94) | func (ea *ExternalAuth) AuthPlain(username, password string) error {
  function New (line 46) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function init (line 103) | func init() {

FILE: internal/auth/external/helperauth.go
  function AuthUsingHelper (line 29) | func AuthUsingHelper(binaryPath, accountName, password string) error {

FILE: internal/auth/ldap/ldap.go
  constant modName (line 22) | modName = "auth.ldap"
  type Auth (line 24) | type Auth struct
    method Configure (line 52) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 125) | func (a *Auth) Name() string {
    method InstanceName (line 129) | func (a *Auth) InstanceName() string {
    method newConn (line 133) | func (a *Auth) newConn() (*ldap.Conn, error) {
    method getConn (line 175) | func (a *Auth) getConn() (*ldap.Conn, error) {
    method returnConn (line 199) | func (a *Auth) returnConn(conn *ldap.Conn) {
    method Lookup (line 216) | func (a *Auth) Lookup(_ context.Context, username string) (string, boo...
    method AuthPlain (line 248) | func (a *Auth) AuthPlain(username, password string) error {
    method Start (line 284) | func (a *Auth) Start() error {
    method Stop (line 293) | func (a *Auth) Stop() error {
  function New (line 45) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function readBindDirective (line 96) | func readBindDirective(c *config.Map, n config.Node) (interface{}, error) {
  function init (line 299) | func init() {

FILE: internal/auth/netauth/netauth.go
  constant modName (line 16) | modName = "auth.netauth"
  function init (line 18) | func init() {
  type Auth (line 26) | type Auth struct
    method Configure (line 43) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 65) | func (a *Auth) Name() string {
    method InstanceName (line 72) | func (a *Auth) InstanceName() string {
    method Lookup (line 78) | func (a *Auth) Lookup(ctx context.Context, username string) (string, b...
    method AuthPlain (line 94) | func (a *Auth) AuthPlain(username, password string) error {
    method checkMustGroup (line 108) | func (a *Auth) checkMustGroup(username string) error {
  function New (line 36) | func New(c *container.C, modName, instName string) (module.Module, error) {

FILE: internal/auth/pam/module.go
  type Auth (line 35) | type Auth struct
    method Name (line 50) | func (a *Auth) Name() string {
    method InstanceName (line 54) | func (a *Auth) InstanceName() string {
    method Configure (line 58) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method AuthPlain (line 82) | func (a *Auth) AuthPlain(username, password string) error {
  function New (line 43) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function init (line 95) | func init() {

FILE: internal/auth/pam/pam.c
  function conv_func (line 28) | static int conv_func(int num_msg, const struct pam_message **msg, struct...
  function run_pam_auth (line 49) | struct error_obj run_pam_auth(const char *username, char *password) {

FILE: internal/auth/pam/pam.go
  constant canCallDirectly (line 39) | canCallDirectly = true
  function runPAMAuth (line 43) | func runPAMAuth(username, password string) error {

FILE: internal/auth/pam/pam.h
  type error_obj (line 21) | struct error_obj {
  type error_obj (line 27) | struct error_obj

FILE: internal/auth/pam/pam_stub.go
  constant canCallDirectly (line 28) | canCallDirectly = false
  function runPAMAuth (line 32) | func runPAMAuth(username, password string) error {

FILE: internal/auth/pass_table/hash.go
  constant HashSHA256 (line 36) | HashSHA256 = "sha256"
  constant HashBcrypt (line 37) | HashBcrypt = "bcrypt"
  constant HashArgon2 (line 38) | HashArgon2 = "argon2"
  constant DefaultHash (line 40) | DefaultHash = HashBcrypt
  constant Argon2Salt (line 42) | Argon2Salt = 16
  constant Argon2Size (line 43) | Argon2Size = 64
  type HashOpts (line 52) | type HashOpts struct
  type FuncHashCompute (line 61) | type FuncHashCompute
  type FuncHashVerify (line 62) | type FuncHashVerify
  function computeArgon2 (line 78) | func computeArgon2(opts HashOpts, pass string) (string, error) {
  function verifyArgon2 (line 98) | func verifyArgon2(pass, hashSalt string) error {
  function computeSHA256 (line 129) | func computeSHA256(_ HashOpts, pass string) (string, error) {
  function verifySHA256 (line 141) | func verifySHA256(pass, hashSalt string) error {
  function computeBcrypt (line 165) | func computeBcrypt(opts HashOpts, pass string) (string, error) {
  function verifyBcrypt (line 173) | func verifyBcrypt(pass, hashSalt string) error {
  function addSHA256 (line 177) | func addSHA256() {

FILE: internal/auth/pass_table/table.go
  type Auth (line 35) | type Auth struct
    method Configure (line 49) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 59) | func (a *Auth) Name() string {
    method InstanceName (line 63) | func (a *Auth) InstanceName() string {
    method Lookup (line 67) | func (a *Auth) Lookup(ctx context.Context, username string) (string, b...
    method AuthPlain (line 76) | func (a *Auth) AuthPlain(username, password string) error {
    method ListUsers (line 101) | func (a *Auth) ListUsers() ([]string, error) {
    method CreateUser (line 114) | func (a *Auth) CreateUser(username, password string) error {
    method CreateUserHash (line 120) | func (a *Auth) CreateUserHash(username, password string, hashAlgo stri...
    method SetUserPassword (line 154) | func (a *Auth) SetUserPassword(username, password string) error {
    method DeleteUser (line 179) | func (a *Auth) DeleteUser(username string) error {
  function New (line 42) | func New(_ *container.C, modName, instName string) (module.Module, error) {
  function init (line 196) | func init() {

FILE: internal/auth/pass_table/table_test.go
  function TestAuth_AuthPlain (line 29) | func TestAuth_AuthPlain(t *testing.T) {

FILE: internal/auth/plain_separate/plain_separate.go
  type Auth (line 34) | type Auth struct
    method Name (line 57) | func (a *Auth) Name() string {
    method InstanceName (line 61) | func (a *Auth) InstanceName() string {
    method Configure (line 65) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method Lookup (line 99) | func (a *Auth) Lookup(ctx context.Context, username string) (string, b...
    method AuthPlain (line 117) | func (a *Auth) AuthPlain(username, password string) error {
  function New (line 46) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function init (line 145) | func init() {

FILE: internal/auth/plain_separate/plain_separate_test.go
  type mockAuth (line 30) | type mockAuth struct
    method SASLMechanisms (line 34) | func (mockAuth) SASLMechanisms() []string {
    method AuthPlain (line 38) | func (m mockAuth) AuthPlain(username, _ string) error {
  type mockTable (line 46) | type mockTable struct
    method Lookup (line 50) | func (m mockTable) Lookup(_ context.Context, a string) (string, bool, ...
  function TestPlainSplit_NoUser (line 55) | func TestPlainSplit_NoUser(t *testing.T) {
  function TestPlainSplit_NoUser_MultiPass (line 72) | func TestPlainSplit_NoUser_MultiPass(t *testing.T) {
  function TestPlainSplit_UserPass (line 94) | func TestPlainSplit_UserPass(t *testing.T) {
  function TestPlainSplit_MultiUser_Pass (line 123) | func TestPlainSplit_MultiUser_Pass(t *testing.T) {

FILE: internal/auth/sasl.go
  type SASLAuth (line 49) | type SASLAuth struct
    method SASLMechanisms (line 62) | func (s *SASLAuth) SASLMechanisms() []string {
    method usernameForAuth (line 75) | func (s *SASLAuth) usernameForAuth(ctx context.Context, saslUsername s...
    method AuthPlain (line 103) | func (s *SASLAuth) AuthPlain(username, password string) error {
    method CreateSASL (line 137) | func (s *SASLAuth) CreateSASL(
    method AddProvider (line 202) | func (s *SASLAuth) AddProvider(m *config.Map, node config.Node) error {
  type ContextData (line 128) | type ContextData struct
  type FailingSASLServ (line 220) | type FailingSASLServ struct
    method Next (line 222) | func (s FailingSASLServ) Next([]byte) ([]byte, bool, error) {

FILE: internal/auth/sasl_test.go
  type mockAuth (line 30) | type mockAuth struct
    method AuthPlain (line 34) | func (m mockAuth) AuthPlain(username, _ string) error {
  function TestCreateSASL (line 42) | func TestCreateSASL(t *testing.T) {

FILE: internal/auth/sasllogin/sasllogin.go
  type LoginAuthenticator (line 8) | type LoginAuthenticator
  type loginState (line 9) | type loginState
  constant loginNotStarted (line 12) | loginNotStarted loginState = iota
  constant loginWaitingUsername (line 13) | loginWaitingUsername
  constant loginWaitingPassword (line 14) | loginWaitingPassword
  type loginServer (line 17) | type loginServer struct
    method Next (line 32) | func (a *loginServer) Next(response []byte) (challenge []byte, done bo...
  function NewLoginServer (line 28) | func NewLoginServer(authenticator LoginAuthenticator) sasl.Server {

FILE: internal/auth/shadow/module.go
  type Auth (line 38) | type Auth struct
    method Name (line 53) | func (a *Auth) Name() string {
    method InstanceName (line 57) | func (a *Auth) InstanceName() string {
    method Configure (line 61) | func (a *Auth) Configure(inlineArgs []string, cfg *config.Map) error {
    method Lookup (line 93) | func (a *Auth) Lookup(username string) (string, bool, error) {
    method AuthPlain (line 113) | func (a *Auth) AuthPlain(username, password string) error {
  function New (line 46) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function init (line 141) | func init() {

FILE: internal/auth/shadow/read.go
  function Read (line 36) | func Read() ([]Entry, error) {
  function parseEntry (line 58) | func parseEntry(line string) (*Entry, error) {
  function Lookup (line 87) | func Lookup(name string) (*Entry, error) {

FILE: internal/auth/shadow/shadow.go
  type Entry (line 23) | type Entry struct

FILE: internal/auth/shadow/verify.go
  constant secsInDay (line 31) | secsInDay = 86400
  method IsAccountValid (line 33) | func (e *Entry) IsAccountValid() bool {
  method IsPasswordValid (line 42) | func (e *Entry) IsPasswordValid() bool {
  method VerifyPassword (line 51) | func (e *Entry) VerifyPassword(pass string) (err error) {

FILE: internal/authz/lookup.go
  function AuthorizeEmailUse (line 11) | func AuthorizeEmailUse(ctx context.Context, username string, addrs []str...

FILE: internal/authz/normalization.go
  type NormalizeFunc (line 10) | type NormalizeFunc
  function NormalizeNoop (line 12) | func NormalizeNoop(s string) (string, error) {
  function NormalizeAuto (line 18) | func NormalizeAuto(s string) (string, error) {

FILE: internal/check/authorize_sender/authorize_sender.go
  constant modName (line 40) | modName = "check.authorize_sender"
  type Check (line 42) | type Check struct
    method Name (line 65) | func (c *Check) Name() string {
    method InstanceName (line 69) | func (c *Check) InstanceName() string {
    method Configure (line 73) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method CheckStateForMsg (line 117) | func (c *Check) CheckStateForMsg(_ context.Context, msgMeta *module.Ms...
  function New (line 58) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type state (line 111) | type state struct
    method authzSender (line 125) | func (s *state) authzSender(ctx context.Context, authName, email strin...
    method CheckConnection (line 208) | func (s *state) CheckConnection(_ context.Context) module.CheckResult {
    method CheckSender (line 212) | func (s *state) CheckSender(ctx context.Context, fromEmail string) mod...
    method CheckRcpt (line 222) | func (s *state) CheckRcpt(_ context.Context, _ string) module.CheckRes...
    method CheckBody (line 226) | func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, _...
    method Close (line 307) | func (s *state) Close() error {
  function init (line 311) | func init() {

FILE: internal/check/command/command.go
  constant modName (line 48) | modName = "check.command"
  type Stage (line 50) | type Stage
  constant StageConnection (line 53) | StageConnection = "conn"
  constant StageSender (line 54) | StageSender     = "sender"
  constant StageRcpt (line 55) | StageRcpt       = "rcpt"
  constant StageBody (line 56) | StageBody       = "body"
  type Check (line 61) | type Check struct
    method Name (line 88) | func (c *Check) Name() string {
    method InstanceName (line 92) | func (c *Check) InstanceName() string {
    method Configure (line 96) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method CheckStateForMsg (line 152) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 71) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type state (line 143) | type state struct
    method expandCommand (line 160) | func (s *state) expandCommand(address string) (string, []string) {
    method run (line 213) | func (s *state) run(cmdName string, args []string, stdin io.Reader) mo...
    method errorRes (line 285) | func (s *state) errorRes(err error, res module.CheckResult, cmdLine st...
    method CheckConnection (line 332) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method CheckSender (line 343) | func (s *state) CheckSender(ctx context.Context, addr string) module.C...
    method CheckRcpt (line 356) | func (s *state) CheckRcpt(ctx context.Context, addr string) module.Che...
    method CheckBody (line 368) | func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, b...
    method Close (line 398) | func (s *state) Close() error {
  function init (line 402) | func init() {

FILE: internal/check/dkim/dkim.go
  type Check (line 45) | type Check struct
    method Configure (line 65) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 96) | func (c *Check) Name() string {
    method InstanceName (line 100) | func (c *Check) InstanceName() string {
    method CheckStateForMsg (line 265) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 57) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type dkimCheckState (line 104) | type dkimCheckState struct
    method CheckConnection (line 110) | func (d *dkimCheckState) CheckConnection(ctx context.Context) module.C...
    method CheckSender (line 114) | func (d *dkimCheckState) CheckSender(ctx context.Context, mailFrom str...
    method CheckRcpt (line 118) | func (d *dkimCheckState) CheckRcpt(ctx context.Context, rcptTo string)...
    method CheckBody (line 122) | func (d *dkimCheckState) CheckBody(ctx context.Context, header textpro...
    method Name (line 257) | func (d *dkimCheckState) Name() string {
    method Close (line 261) | func (d *dkimCheckState) Close() error {
  function init (line 273) | func init() {

FILE: internal/check/dkim/dkim_test.go
  constant unsignedMailString (line 37) | unsignedMailString = `From: Joe SixPack <joe@football.example.com>
  constant dnsPublicKey (line 50) | dnsPublicKey = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" +
  constant verifiedMailString (line 62) | verifiedMailString = `DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=e...
  function testCheck (line 86) | func testCheck(t *testing.T, zones map[string]mockdns.Zone, cfg []config...
  function TestDkimVerify_NoSig (line 103) | func TestDkimVerify_NoSig(t *testing.T) {
  function TestDkimVerify_InvalidSig (line 135) | func TestDkimVerify_InvalidSig(t *testing.T) {
  function TestDkimVerify_ValidSig (line 170) | func TestDkimVerify_ValidSig(t *testing.T) {
  function TestDkimVerify_RequiredFields (line 197) | func TestDkimVerify_RequiredFields(t *testing.T) {
  function TestDkimVerify_BufferOpenFail (line 236) | func TestDkimVerify_BufferOpenFail(t *testing.T) {
  function TestDkimVerify_FailClosed (line 265) | func TestDkimVerify_FailClosed(t *testing.T) {
  function TestDkimVerify_FailOpen (line 312) | func TestDkimVerify_FailOpen(t *testing.T) {

FILE: internal/check/dns/dns.go
  function requireMatchingRDNS (line 32) | func requireMatchingRDNS(ctx check.StatelessCheckContext) module.CheckRe...
  function requireMXRecord (line 88) | func requireMXRecord(ctx check.StatelessCheckContext, mailFrom string) m...
  function init (line 158) | func init() {

FILE: internal/check/dns/dns_test.go
  function TestRequireMatchingRDNS (line 32) | func TestRequireMatchingRDNS(t *testing.T) {
  function TestRequireMXRecord (line 80) | func TestRequireMXRecord(t *testing.T) {

FILE: internal/check/dnsbl/common.go
  type ListedErr (line 31) | type ListedErr struct
    method Fields (line 39) | func (le ListedErr) Fields() map[string]interface{} {
    method Error (line 55) | func (le ListedErr) Error() string {
  function checkDomain (line 59) | func checkDomain(ctx context.Context, resolver dns.Resolver, cfg List, d...
  function matchResponseRules (line 126) | func matchResponseRules(addrs []net.IPAddr, rules []ResponseRule) (score...
  function checkIP (line 154) | func checkIP(ctx context.Context, resolver dns.Resolver, cfg List, ip ne...
  function queryString (line 248) | func queryString(ip net.IP) string {

FILE: internal/check/dnsbl/common_test.go
  function TestQueryString (line 30) | func TestQueryString(t *testing.T) {
  function TestCheckDomain (line 50) | func TestCheckDomain(t *testing.T) {
  function TestCheckIP (line 119) | func TestCheckIP(t *testing.T) {
  function TestCheckDomainWithResponseRules (line 240) | func TestCheckDomainWithResponseRules(t *testing.T) {

FILE: internal/check/dnsbl/dnsbl.go
  type ResponseRule (line 43) | type ResponseRule struct
  type List (line 49) | type List struct
  type DNSBL (line 68) | type DNSBL struct
    method Name (line 89) | func (bl *DNSBL) Name() string {
    method InstanceName (line 93) | func (bl *DNSBL) InstanceName() string {
    method Configure (line 97) | func (bl *DNSBL) Configure(inlineArgs []string, cfg *config.Map) error {
    method readListCfg (line 124) | func (bl *DNSBL) readListCfg(node config.Node) error {
    method testList (line 235) | func (bl *DNSBL) testList(listCfg List) {
    method checkList (line 312) | func (bl *DNSBL) checkList(ctx context.Context, list List, ip net.IP, ...
    method checkLists (line 351) | func (bl *DNSBL) checkLists(ctx context.Context, ip net.IP, ehlo, mail...
    method CheckConnection (line 443) | func (bl *DNSBL) CheckConnection(ctx context.Context, state *module.Co...
    method CheckStateForMsg (line 470) | func (bl *DNSBL) CheckStateForMsg(ctx context.Context, msgMeta *module...
  function New (line 80) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function parseResponseRule (line 197) | func parseResponseRule(node config.Node) (ResponseRule, error) {
  type state (line 464) | type state struct
    method CheckConnection (line 478) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method CheckSender (line 494) | func (*state) CheckSender(context.Context, string) module.CheckResult {
    method CheckRcpt (line 498) | func (*state) CheckRcpt(context.Context, string) module.CheckResult {
    method CheckBody (line 502) | func (*state) CheckBody(context.Context, textproto.Header, buffer.Buff...
    method Close (line 506) | func (*state) Close() error {
  function init (line 510) | func init() {

FILE: internal/check/dnsbl/dnsbl_test.go
  function TestCheckList (line 31) | func TestCheckList(t *testing.T) {
  function TestCheckLists (line 122) | func TestCheckLists(t *testing.T) {
  function TestCheckIPWithResponseRules (line 215) | func TestCheckIPWithResponseRules(t *testing.T) {
  function TestCheckListsWithResponseRules (line 376) | func TestCheckListsWithResponseRules(t *testing.T) {

FILE: internal/check/milter/milter.go
  constant modName (line 41) | modName = "check.milter"
  type Check (line 43) | type Check struct
    method Name (line 60) | func (c *Check) Name() string {
    method InstanceName (line 64) | func (c *Check) InstanceName() string {
    method Configure (line 68) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method CheckStateForMsg (line 119) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 51) | func New(c *container.C, _, instName string) (module.Module, error) {
  type state (line 111) | type state struct
    method handleAction (line 132) | func (s *state) handleAction(act *milter.Action) module.CheckResult {
    method apply (line 191) | func (s *state) apply(modifyActs []milter.ModifyAction, res module.Che...
    method CheckConnection (line 228) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method ioError (line 328) | func (s *state) ioError(err error) module.CheckResult {
    method CheckSender (line 350) | func (s *state) CheckSender(ctx context.Context, mailFrom string) modu...
    method CheckRcpt (line 377) | func (s *state) CheckRcpt(ctx context.Context, rcptTo string) module.C...
    method CheckBody (line 389) | func (s *state) CheckBody(ctx context.Context, header textproto.Header...
    method Close (line 439) | func (s *state) Close() error {
  function init (line 448) | func init() {

FILE: internal/check/milter/milter_test.go
  function TestAcceptValidEndpoints (line 27) | func TestAcceptValidEndpoints(t *testing.T) {
  function TestRejectInvalidEndpoints (line 49) | func TestRejectInvalidEndpoints(t *testing.T) {

FILE: internal/check/requiretls/requiretls.go
  function requireTLS (line 28) | func requireTLS(ctx check.StatelessCheckContext) module.CheckResult {
  function init (line 43) | func init() {

FILE: internal/check/rspamd/rspamd.go
  constant modName (line 46) | modName = "check.rspamd"
  type Check (line 48) | type Check struct
    method Name (line 78) | func (c *Check) Name() string {
    method InstanceName (line 82) | func (c *Check) InstanceName() string {
    method Configure (line 86) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method CheckStateForMsg (line 157) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 68) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type state (line 148) | type state struct
    method CheckConnection (line 165) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method CheckSender (line 169) | func (s *state) CheckSender(ctx context.Context, addr string) module.C...
    method CheckRcpt (line 174) | func (s *state) CheckRcpt(ctx context.Context, addr string) module.Che...
    method CheckBody (line 218) | func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, b...
    method Close (line 378) | func (s *state) Close() error {
  function addConnHeaders (line 179) | func addConnHeaders(r *http.Request, meta *module.MsgMetadata, mailFrom ...
  type response (line 368) | type response struct
  function init (line 382) | func init() {

FILE: internal/check/skeleton.go
  constant modName (line 41) | modName = "check_things"
  type Check (line 43) | type Check struct
    method Name (line 54) | func (c *Check) Name() string {
    method InstanceName (line 58) | func (c *Check) InstanceName() string {
    method Init (line 62) | func (c *Check) Init(cfg *config.Map) error {
    method CheckStateForMsg (line 72) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 48) | func New(modName, instName string, aliases, inlineArgs []string) (module...
  type state (line 66) | type state struct
    method CheckConnection (line 80) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method CheckSender (line 84) | func (s *state) CheckSender(ctx context.Context, addr string) module.C...
    method CheckRcpt (line 88) | func (s *state) CheckRcpt(ctx context.Context, addr string) module.Che...
    method CheckBody (line 92) | func (s *state) CheckBody(ctx context.Context, hdr textproto.Header, b...
    method Close (line 96) | func (s *state) Close() error {
  function init (line 100) | func init() {

FILE: internal/check/spf/spf.go
  constant modName (line 48) | modName = "check.spf"
  type Check (line 50) | type Check struct
    method Name (line 73) | func (c *Check) Name() string {
    method InstanceName (line 77) | func (c *Check) InstanceName() string {
    method Configure (line 81) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method CheckStateForMsg (line 130) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
  function New (line 65) | func New(c *container.C, _, instName string) (module.Module, error) {
  type spfRes (line 116) | type spfRes struct
  type state (line 121) | type state struct
    method spfResult (line 139) | func (s *state) spfResult(res spf.Result, err error) module.CheckResult {
    method relyOnDMARC (line 243) | func (s *state) relyOnDMARC(ctx context.Context, hdr textproto.Header)...
    method CheckConnection (line 303) | func (s *state) CheckConnection(ctx context.Context) module.CheckResult {
    method CheckSender (line 371) | func (s *state) CheckSender(ctx context.Context, mailFrom string) modu...
    method CheckRcpt (line 375) | func (s *state) CheckRcpt(ctx context.Context, rcptTo string) module.C...
    method CheckBody (line 379) | func (s *state) CheckBody(ctx context.Context, header textproto.Header...
    method Close (line 416) | func (s *state) Close() error {
  function prepareMailFrom (line 270) | func prepareMailFrom(from string) (string, error) {
  function init (line 420) | func init() {

FILE: internal/check/stateless_check.go
  type StatelessCheckContext (line 39) | type StatelessCheckContext struct
  type FuncConnCheck (line 54) | type FuncConnCheck
  type FuncSenderCheck (line 55) | type FuncSenderCheck
  type FuncRcptCheck (line 56) | type FuncRcptCheck
  type FuncBodyCheck (line 57) | type FuncBodyCheck
  type statelessCheck (line 60) | type statelessCheck struct
    method CheckStateForMsg (line 150) | func (c *statelessCheck) CheckStateForMsg(ctx context.Context, msgMeta...
    method Configure (line 157) | func (c *statelessCheck) Configure(inlineArgs []string, cfg *config.Ma...
    method Name (line 171) | func (c *statelessCheck) Name() string {
    method InstanceName (line 175) | func (c *statelessCheck) InstanceName() string {
  type statelessCheckState (line 77) | type statelessCheckState struct
    method String (line 82) | func (s *statelessCheckState) String() string {
    method CheckConnection (line 86) | func (s *statelessCheckState) CheckConnection(ctx context.Context) mod...
    method CheckSender (line 101) | func (s *statelessCheckState) CheckSender(ctx context.Context, mailFro...
    method CheckRcpt (line 116) | func (s *statelessCheckState) CheckRcpt(ctx context.Context, rcptTo st...
    method CheckBody (line 131) | func (s *statelessCheckState) CheckBody(ctx context.Context, header te...
    method Close (line 146) | func (s *statelessCheckState) Close() error {
  function RegisterStatelessCheck (line 189) | func RegisterStatelessCheck(name string, defaultFailAction modconfig.Fai...

FILE: internal/cli/app.go
  function init (line 14) | func init() {
  function AddGlobalFlag (line 77) | func AddGlobalFlag(f cli.Flag) {
  function AddSubcommand (line 81) | func AddSubcommand(cmd *cli.Command) {
  function RunWithoutExit (line 87) | func RunWithoutExit() int {
  function Run (line 100) | func Run() {

FILE: internal/cli/clitools/clitools.go
  function Confirmation (line 30) | func Confirmation(prompt string, def bool) bool {
  function readPass (line 52) | func readPass(tty *os.File, output []byte) ([]byte, error) {
  function ReadPassword (line 89) | func ReadPassword(prompt string) (string, error) {

FILE: internal/cli/clitools/termios.go
  type Termios (line 34) | type Termios struct
  function TurnOnRawIO (line 48) | func TurnOnRawIO(tty *os.File) (orig Termios, err error) {
  function TcSetAttr (line 67) | func TcSetAttr(fd uintptr, termios *Termios) error {
  function TcGetAttr (line 75) | func TcGetAttr(fd uintptr) (*Termios, error) {

FILE: internal/cli/clitools/termios_stub.go
  type Termios (line 29) | type Termios struct
  function TurnOnRawIO (line 39) | func TurnOnRawIO(tty *os.File) (orig Termios, err error) {
  function TcSetAttr (line 43) | func TcSetAttr(fd uintptr, termios *Termios) error {
  function TcGetAttr (line 47) | func TcGetAttr(fd uintptr) (*Termios, error) {

FILE: internal/cli/ctl/appendlimit.go
  type AppendLimitUser (line 33) | type AppendLimitUser interface
  function imapAcctAppendlimit (line 41) | func imapAcctAppendlimit(be module.Storage, ctx *cli.Context) error {

FILE: internal/cli/ctl/hash.go
  function init (line 33) | func init() {
  function hashCommand (line 74) | func hashCommand(ctx *cli.Context) error {

FILE: internal/cli/ctl/imap.go
  function init (line 38) | func init() {
  function FormatAddress (line 412) | func FormatAddress(addr *imap.Address) string {
  function FormatAddressList (line 416) | func FormatAddressList(addrs []*imap.Address) string {
  function mboxesList (line 424) | func mboxesList(be module.Storage, ctx *cli.Context) error {
  function mboxesCreate (line 455) | func mboxesCreate(be module.Storage, ctx *cli.Context) error {
  function mboxesRemove (line 485) | func mboxesRemove(be module.Storage, ctx *cli.Context) error {
  function mboxesRename (line 518) | func mboxesRename(be module.Storage, ctx *cli.Context) error {
  function msgsAdd (line 540) | func msgsAdd(be module.Storage, ctx *cli.Context) error {
  function msgsRemove (line 589) | func msgsRemove(be module.Storage, ctx *cli.Context) error {
  function msgsCopy (line 632) | func msgsCopy(be module.Storage, ctx *cli.Context) error {
  function msgsMove (line 672) | func msgsMove(be module.Storage, ctx *cli.Context) error {
  function msgsList (line 714) | func msgsList(be module.Storage, ctx *cli.Context) error {
  function msgsDump (line 794) | func msgsDump(be module.Storage, ctx *cli.Context) error {
  function msgsFlags (line 842) | func msgsFlags(be module.Storage, ctx *cli.Context) error {

FILE: internal/cli/ctl/imapacct.go
  function init (line 33) | func init() {
  type SpecialUseUser (line 193) | type SpecialUseUser interface
  function imapAcctList (line 197) | func imapAcctList(be module.Storage, ctx *cli.Context) error {
  function imapAcctCreate (line 218) | func imapAcctCreate(be module.Storage, ctx *cli.Context) error {
  function imapAcctRemove (line 283) | func imapAcctRemove(be module.Storage, ctx *cli.Context) error {

FILE: internal/cli/ctl/moduleinit.go
  function closeIfNeeded (line 34) | func closeIfNeeded(i any) {
  type managedStorage (line 42) | type managedStorage struct
    method Close (line 47) | func (m *managedStorage) Close() error {
  type managedUserDB (line 57) | type managedUserDB struct
    method Close (line 62) | func (m *managedUserDB) Close() error {
  function getCfgBlockModule (line 72) | func getCfgBlockModule(ctx *cli.Context) (*container.C, module.Module, e...
  function openStorage (line 121) | func openStorage(ctx *cli.Context) (module.Storage, error) {
  function openUserDB (line 156) | func openUserDB(ctx *cli.Context) (module.PlainUserDB, error) {

FILE: internal/cli/ctl/users.go
  function init (line 35) | func init() {
  function usersList (line 169) | func usersList(be module.PlainUserDB, ctx *cli.Context) error {
  function usersCreate (line 185) | func usersCreate(be module.PlainUserDB, ctx *cli.Context) error {
  function usersRemove (line 213) | func usersRemove(be module.PlainUserDB, ctx *cli.Context) error {
  function usersPassword (line 228) | func usersPassword(be module.PlainUserDB, ctx *cli.Context) error {

FILE: internal/cli/extflag.go
  type extFlag (line 10) | type extFlag struct
    method Apply (line 14) | func (e *extFlag) Apply(fs *flag.FlagSet) error {
    method Names (line 19) | func (e *extFlag) Names() []string {
    method IsSet (line 23) | func (e *extFlag) IsSet() bool {
    method String (line 27) | func (e *extFlag) String() string {
    method IsVisible (line 31) | func (e *extFlag) IsVisible() bool {
    method TakesValue (line 35) | func (e *extFlag) TakesValue() bool {
    method GetUsage (line 39) | func (e *extFlag) GetUsage() string {
    method GetValue (line 43) | func (e *extFlag) GetValue() string {
    method GetDefaultText (line 47) | func (e *extFlag) GetDefaultText() string {
    method GetEnvVars (line 51) | func (e *extFlag) GetEnvVars() []string {
  function mapStdlibFlags (line 55) | func mapStdlibFlags(app *cli.App) {

FILE: internal/dmarc/dmarc.go
  type Resolver (line 28) | type Resolver interface
  constant PolicyNone (line 39) | PolicyNone       = dmarc.PolicyNone
  constant PolicyReject (line 40) | PolicyReject     = dmarc.PolicyReject
  constant PolicyQuarantine (line 41) | PolicyQuarantine = dmarc.PolicyQuarantine

FILE: internal/dmarc/evaluate.go
  function FetchRecord (line 40) | func FetchRecord(ctx context.Context, r Resolver, fromDomain string) (po...
  type EvalResult (line 90) | type EvalResult struct
  function EvaluateAlignment (line 118) | func EvaluateAlignment(fromDomain string, record *Record, results []auth...
  function isAligned (line 205) | func isAligned(fromDomain, authDomain string, mode AlignmentMode) bool {
  function ExtractFromDomain (line 226) | func ExtractFromDomain(hdr textproto.Header) (string, error) {

FILE: internal/dmarc/evaluate_test.go
  function TestEvaluateAlignment (line 31) | func TestEvaluateAlignment(t *testing.T) {
  function TestExtractDomains (line 459) | func TestExtractDomains(t *testing.T) {

FILE: internal/dmarc/verifier.go
  type verifyData (line 33) | type verifyData struct
  type errPanic (line 42) | type errPanic struct
    method Error (line 46) | func (errPanic) Error() string {
  type Verifier (line 54) | type Verifier struct
    method Close (line 73) | func (v *Verifier) Close() error {
    method FetchRecord (line 84) | func (v *Verifier) FetchRecord(ctx context.Context, header textproto.H...
    method Apply (line 129) | func (v *Verifier) Apply(authRes []authres.Result) (EvalResult, Policy) {
  function NewVerifier (line 66) | func NewVerifier(r Resolver) *Verifier {

FILE: internal/dmarc/verifier_test.go
  function TestDMARC (line 35) | func TestDMARC(t *testing.T) {

FILE: internal/dsn/dsn.go
  type ReportingMTAInfo (line 38) | type ReportingMTAInfo struct
    method WriteTo (line 55) | func (info ReportingMTAInfo) WriteTo(utf8 bool, w io.Writer) error {
  type Action (line 106) | type Action
  constant ActionFailed (line 109) | ActionFailed    Action = "failed"
  constant ActionDelayed (line 110) | ActionDelayed   Action = "delayed"
  constant ActionDelivered (line 111) | ActionDelivered Action = "delivered"
  constant ActionRelayed (line 112) | ActionRelayed   Action = "relayed"
  constant ActionExpanded (line 113) | ActionExpanded  Action = "expanded"
  type RecipientInfo (line 116) | type RecipientInfo struct
    method WriteTo (line 127) | func (info RecipientInfo) WriteTo(utf8 bool, w io.Writer) error {
  type Envelope (line 182) | type Envelope struct
  function GenerateDSN (line 191) | func GenerateDSN(utf8 bool, envelope Envelope, mtaInfo ReportingMTAInfo,...
  function writeHeader (line 217) | func writeHeader(utf8 bool, w *textproto.MultipartWriter, header textpro...
  function writeMachineReadablePart (line 233) | func writeMachineReadablePart(utf8 bool, w *textproto.MultipartWriter, m...
  function writeHumanReadablePart (line 275) | func writeHumanReadablePart(w *textproto.MultipartWriter, mtaInfo Report...

FILE: internal/endpoint/dovecot_sasld/dovecot_sasl.go
  constant modName (line 40) | modName = "dovecot_sasld"
  type Endpoint (line 42) | type Endpoint struct
    method Name (line 64) | func (endp *Endpoint) Name() string {
    method InstanceName (line 68) | func (endp *Endpoint) InstanceName() string {
    method Configure (line 72) | func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error {
    method Start (line 111) | func (endp *Endpoint) Start() error {
    method Stop (line 132) | func (endp *Endpoint) Stop() error {
  function New (line 53) | func New(c *container.C, _ string, addrs []string) (container.LifetimeMo...
  function init (line 137) | func init() {

FILE: internal/endpoint/imap/imap.go
  type Endpoint (line 54) | type Endpoint struct
    method Configure (line 86) | func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error {
    method Start (line 165) | func (endp *Endpoint) Start() error {
    method setupListeners (line 181) | func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error {
    method Name (line 215) | func (endp *Endpoint) Name() string {
    method InstanceName (line 219) | func (endp *Endpoint) InstanceName() string {
    method Stop (line 223) | func (endp *Endpoint) Stop() error {
    method usernameForStorage (line 236) | func (endp *Endpoint) usernameForStorage(ctx context.Context, saslUser...
    method openAccount (line 261) | func (endp *Endpoint) openAccount(c imapserver.Conn, identity string) ...
    method Login (line 281) | func (endp *Endpoint) Login(connInfo *imap.ConnInfo, username, passwor...
    method I18NLevel (line 301) | func (endp *Endpoint) I18NLevel() int {
    method enableExtensions (line 309) | func (endp *Endpoint) enableExtensions() error {
    method SupportedThreadAlgorithms (line 329) | func (endp *Endpoint) SupportedThreadAlgorithms() []sortthread.ThreadA...
  function New (line 73) | func New(c *container.C, modName string, addrs []string) (container.Life...
  function init (line 338) | func init() {

FILE: internal/endpoint/openmetrics/om.go
  constant modName (line 35) | modName = "openmetrics"
  type Endpoint (line 37) | type Endpoint struct
    method Configure (line 54) | func (e *Endpoint) Configure(inlineArgs []string, cfg *config.Map) err...
    method Name (line 78) | func (e *Endpoint) Name() string {
    method InstanceName (line 82) | func (e *Endpoint) InstanceName() string {
    method Start (line 86) | func (e *Endpoint) Start() error {
    method Stop (line 109) | func (e *Endpoint) Stop() error {
  function New (line 47) | func New(c *container.C, _ string, args []string) (container.LifetimeMod...
  function init (line 117) | func init() {

FILE: internal/endpoint/smtp/date.go
  function parseMessageDateTime (line 57) | func parseMessageDateTime(maybeDate string) (time.Time, error) {

FILE: internal/endpoint/smtp/metrics.go
  function init (line 81) | func init() {

FILE: internal/endpoint/smtp/session.go
  function limitReader (line 45) | func limitReader(r io.Reader, n int64, err error) *limitedReader {
  type limitedReader (line 49) | type limitedReader struct
    method Read (line 58) | func (l *limitedReader) Read(p []byte) (n int, err error) {
  type Session (line 73) | type Session struct
    method AuthMechanisms (line 101) | func (s *Session) AuthMechanisms() []string {
    method Auth (line 105) | func (s *Session) Auth(mech string) (sasl.Server, error) {
    method Reset (line 113) | func (s *Session) Reset() {
    method releaseLimits (line 123) | func (s *Session) releaseLimits() {
    method abort (line 140) | func (s *Session) abort(ctx context.Context) {
    method cleanSession (line 149) | func (s *Session) cleanSession() {
    method AuthPlain (line 161) | func (s *Session) AuthPlain(username, password string) error {
    method startDelivery (line 183) | func (s *Session) startDelivery(ctx context.Context, from string, opts...
    method Mail (line 277) | func (s *Session) Mail(from string, opts *smtp.MailOptions) error {
    method fetchRDNSName (line 303) | func (s *Session) fetchRDNSName(ctx context.Context) {
    method Rcpt (line 336) | func (s *Session) Rcpt(to string, opts *smtp.RcptOptions) error {
    method rcpt (line 378) | func (s *Session) rcpt(ctx context.Context, to string, opts *smtp.Rcpt...
    method Logout (line 400) | func (s *Session) Logout() error {
    method prepareBody (line 420) | func (s *Session) prepareBody(r io.Reader) (textproto.Header, buffer.B...
    method Data (line 451) | func (s *Session) Data(r io.Reader) error {
    method LMTPData (line 506) | func (s *Session) LMTPData(r io.Reader, sc smtp.StatusCollector) error {
    method checkRoutingLoops (line 552) | func (s *Session) checkRoutingLoops(header textproto.Header) error {
  type statusWrapper (line 497) | type statusWrapper struct
    method SetStatus (line 502) | func (sw statusWrapper) SetStatus(rcpt string, err error) {
  method wrapErr (line 572) | func (endp *Endpoint) wrapErr(msgId string, mangleUTF8 bool, command str...

FILE: internal/endpoint/smtp/smtp.go
  type Endpoint (line 56) | type Endpoint struct
    method Name (line 86) | func (endp *Endpoint) Name() string {
    method InstanceName (line 90) | func (endp *Endpoint) InstanceName() string {
    method Configure (line 111) | func (endp *Endpoint) Configure(_ []string, cfg *config.Map) error {
    method setConfig (line 239) | func (endp *Endpoint) setConfig(cfg *config.Map) error {
    method Start (line 324) | func (endp *Endpoint) Start() error {
    method authErrorMap (line 334) | func (endp *Endpoint) authErrorMap(err error) error {
    method setupListeners (line 350) | func (endp *Endpoint) setupListeners(addresses []config.Endpoint) error {
    method NewSession (line 385) | func (endp *Endpoint) NewSession(conn *smtp.Conn) (smtp.Session, error) {
    method newSession (line 401) | func (endp *Endpoint) newSession(conn *smtp.Conn) *Session {
    method ConnectionCount (line 444) | func (endp *Endpoint) ConnectionCount() int {
    method Stop (line 448) | func (endp *Endpoint) Stop() error {
  function New (line 94) | func New(c *container.C, modName string, addrs []string) (container.Life...
  function autoBufferMode (line 153) | func autoBufferMode(maxSize int, dir string) func(io.Reader) (buffer.Buf...
  function bufferModeDirective (line 185) | func bufferModeDirective(_ *config.Map, node config.Node) (interface{}, ...
  function init (line 461) | func init() {

FILE: internal/endpoint/smtp/smtp_test.go
  constant testMsg (line 48) | testMsg = "From: <sender@example.org>\r\n" +
  function testEndpoint (line 53) | func testEndpoint(t *testing.T, modName string, authMod module.PlainAuth...
  function submitMsg (line 121) | func submitMsg(t *testing.T, cl *smtp.Client, from string, rcpts []strin...
  function submitMsgOpts (line 125) | func submitMsgOpts(t *testing.T, cl *smtp.Client, from string, rcpts []s...
  function TestSMTPDelivery (line 150) | func TestSMTPDelivery(t *testing.T) {
  function TestSMTPDelivery_rDNSError (line 192) | func TestSMTPDelivery_rDNSError(t *testing.T) {
  function TestSMTPDelivery_EarlyCheck_Fail (line 233) | func TestSMTPDelivery_EarlyCheck_Fail(t *testing.T) {
  function TestSMTPDeliver_CheckError (line 273) | func TestSMTPDeliver_CheckError(t *testing.T) {
  function TestSMTPDeliver_CheckError_Deferred (line 316) | func TestSMTPDeliver_CheckError_Deferred(t *testing.T) {
  function TestSMTPDelivery_Multi (line 370) | func TestSMTPDelivery_Multi(t *testing.T) {
  function TestSMTPDelivery_AbortData (line 412) | func TestSMTPDelivery_AbortData(t *testing.T) {
  function TestSMTPDelivery_EmptyMessage (line 454) | func TestSMTPDelivery_EmptyMessage(t *testing.T) {
  function TestSMTPDelivery_AbortLogout (line 497) | func TestSMTPDelivery_AbortLogout(t *testing.T) {
  function TestSMTPDelivery_Reset (line 532) | func TestSMTPDelivery_Reset(t *testing.T) {
  function TestSMTPDelivery_SubmissionAuthRequire (line 571) | func TestSMTPDelivery_SubmissionAuthRequire(t *testing.T) {
  function TestSMTPDelivery_SubmissionAuthOK (line 591) | func TestSMTPDelivery_SubmissionAuthOK(t *testing.T) {
  function TestMain (line 637) | func TestMain(m *testing.M) {

FILE: internal/endpoint/smtp/smtputf8_test.go
  function TestSMTPUTF8_MangleStatusMessage (line 34) | func TestSMTPUTF8_MangleStatusMessage(t *testing.T) {
  function TestSMTP_RejectNonASCIIFrom (line 78) | func TestSMTP_RejectNonASCIIFrom(t *testing.T) {
  function TestSMTPUTF8_NormalizeCaseFoldFrom (line 109) | func TestSMTPUTF8_NormalizeCaseFoldFrom(t *testing.T) {
  function TestSMTP_RejectNonASCIIRcpt (line 140) | func TestSMTP_RejectNonASCIIRcpt(t *testing.T) {
  function TestSMTPUTF8_NormalizeCaseFoldRcpt (line 171) | func TestSMTPUTF8_NormalizeCaseFoldRcpt(t *testing.T) {
  function TestSMTPUTF8_NoMangleStatusMessage (line 202) | func TestSMTPUTF8_NoMangleStatusMessage(t *testing.T) {
  function TestSMTPUTF8_Received_EHLO_ALabel (line 248) | func TestSMTPUTF8_Received_EHLO_ALabel(t *testing.T) {
  function TestSMTPUTF8_Received_rDNS_ALabel (line 286) | func TestSMTPUTF8_Received_rDNS_ALabel(t *testing.T) {
  function TestSMTPUTF8_Received_rDNS_ULabel (line 324) | func TestSMTPUTF8_Received_rDNS_ULabel(t *testing.T) {
  function TestSMTPUTF8_Received_EHLO_ULabel (line 364) | func TestSMTPUTF8_Received_EHLO_ULabel(t *testing.T) {

FILE: internal/endpoint/smtp/submission.go
  method submissionPrepare (line 45) | func (s *Session) submissionPrepare(msgMeta *module.MsgMetadata, header ...

FILE: internal/endpoint/smtp/submission_test.go
  function init (line 33) | func init() {
  function TestSubmissionPrepare (line 43) | func TestSubmissionPrepare(t *testing.T) {

FILE: internal/imap_filter/command/command.go
  constant modName (line 41) | modName = "imap.filter.command"
  type Check (line 45) | type Check struct
    method IMAPFilter (line 53) | func (c *Check) IMAPFilter(accountName string, rcptTo string, msgMeta ...
    method Name (line 75) | func (c *Check) Name() string {
    method InstanceName (line 79) | func (c *Check) InstanceName() string {
    method Configure (line 83) | func (c *Check) Configure(inlineArgs []string, cfg *config.Map) error {
    method expandCommand (line 100) | func (c *Check) expandCommand(msgMeta *module.MsgMetadata, accountName...
    method run (line 161) | func (c *Check) run(cmdName string, args []string, stdin io.Reader) (s...
  function New (line 66) | func New(c *container.C, _, instName string) (module.Module, error) {
  function init (line 207) | func init() {

FILE: internal/imap_filter/group.go
  type Group (line 36) | type Group struct
    method IMAPFilter (line 49) | func (g *Group) IMAPFilter(accountName string, rcptTo string, meta *mo...
    method Configure (line 71) | func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 84) | func (g *Group) Name() string {
    method InstanceName (line 88) | func (g *Group) InstanceName() string {
  function NewGroup (line 42) | func NewGroup(c *container.C, modName, instName string) (module.Module, ...
  function init (line 92) | func init() {

FILE: internal/libdns/acmedns.go
  function init (line 14) | func init() {

FILE: internal/libdns/alidns.go
  function init (line 14) | func init() {

FILE: internal/libdns/cloudflare.go
  function init (line 14) | func init() {

FILE: internal/libdns/digitalocean.go
  function init (line 14) | func init() {

FILE: internal/libdns/gandi.go
  function init (line 17) | func init() {

FILE: internal/libdns/gcore.go
  function init (line 15) | func init() {

FILE: internal/libdns/googleclouddns.go
  function init (line 14) | func init() {

FILE: internal/libdns/hetzner.go
  function init (line 15) | func init() {

FILE: internal/libdns/leaseweb.go
  function init (line 15) | func init() {

FILE: internal/libdns/metaname.go
  function init (line 14) | func init() {

FILE: internal/libdns/namecheap.go
  function init (line 14) | func init() {

FILE: internal/libdns/namedotcom.go
  function init (line 15) | func init() {

FILE: internal/libdns/provider_module.go
  type ProviderModule (line 8) | type ProviderModule struct
    method Configure (line 18) | func (p *ProviderModule) Configure(inlineArgs []string, cfg *config.Ma...
    method Name (line 29) | func (p *ProviderModule) Name() string {
    method InstanceName (line 33) | func (p *ProviderModule) InstanceName() string {

FILE: internal/libdns/rfc2136.go
  function init (line 14) | func init() {

FILE: internal/libdns/route53.go
  function init (line 14) | func init() {

FILE: internal/libdns/vultr.go
  function init (line 15) | func init() {

FILE: internal/limits/limiters/bucket.go
  type BucketSet (line 39) | type BucketSet struct
    method Close (line 72) | func (r *BucketSet) Close() {
    method take (line 81) | func (r *BucketSet) take(key string) L {
    method Take (line 122) | func (r *BucketSet) Take(key string) bool {
    method Release (line 131) | func (r *BucketSet) Release(key string) {
    method TakeContext (line 146) | func (r *BucketSet) TakeContext(ctx context.Context, key string) error {
  function NewBucketSet (line 60) | func NewBucketSet(new_ func() L, reapInterval time.Duration, maxBuckets ...

FILE: internal/limits/limiters/concurrency.go
  type Semaphore (line 28) | type Semaphore struct
    method Take (line 36) | func (s Semaphore) Take() bool {
    method TakeContext (line 44) | func (s Semaphore) TakeContext(ctx context.Context) error {
    method Release (line 56) | func (s Semaphore) Release() {
    method Close (line 67) | func (s Semaphore) Close() {
  function NewSemaphore (line 32) | func NewSemaphore(max int) Semaphore {

FILE: internal/limits/limiters/limiters.go
  type L (line 28) | type L interface

FILE: internal/limits/limiters/multilimit.go
  type MultiLimit (line 27) | type MultiLimit struct
    method Take (line 31) | func (ml *MultiLimit) Take() bool {
    method TakeContext (line 45) | func (ml *MultiLimit) TakeContext(ctx context.Context) error {
    method Release (line 59) | func (ml *MultiLimit) Release() {
    method Close (line 65) | func (ml *MultiLimit) Close() {

FILE: internal/limits/limiters/rate.go
  type Rate (line 39) | type Rate struct
    method fill (line 62) | func (r Rate) fill(burstSize int, interval time.Duration) {
    method Take (line 87) | func (r Rate) Take() bool {
    method TakeContext (line 96) | func (r Rate) TakeContext(ctx context.Context) error {
    method Release (line 112) | func (r Rate) Release() {
    method Close (line 115) | func (r Rate) Close() {
  function NewRate (line 44) | func NewRate(burstSize int, interval time.Duration) Rate {

FILE: internal/limits/limits.go
  type Group (line 42) | type Group struct
    method Configure (line 57) | func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error {
    method TakeMsg (line 176) | func (g *Group) TakeMsg(ctx context.Context, addr net.IP, sourceDomain...
    method TakeDest (line 200) | func (g *Group) TakeDest(ctx context.Context, domain string) error {
    method ReleaseMsg (line 209) | func (g *Group) ReleaseMsg(addr net.IP, sourceDomain string) {
    method ReleaseDest (line 219) | func (g *Group) ReleaseDest(domain string) {
    method Name (line 226) | func (g *Group) Name() string {
    method InstanceName (line 230) | func (g *Group) InstanceName() string {
  function New (line 51) | func New(c *container.C, _, instName string) (module.Module, error) {
  function rateCtor (line 134) | func rateCtor(node config.Node, args []string) (func() limiters.L, error) {
  function concurrencyCtor (line 163) | func concurrencyCtor(node config.Node, args []string) (func() limiters.L...
  function init (line 234) | func init() {

FILE: internal/modify/dkim/dkim.go
  constant Day (line 47) | Day = 86400 * time.Second
  type Modifier (line 98) | type Modifier struct
    method Name (line 126) | func (m *Modifier) Name() string {
    method InstanceName (line 130) | func (m *Modifier) InstanceName() string {
    method Configure (line 134) | func (m *Modifier) Configure(inlineArgs []string, cfg *config.Map) err...
    method fieldsToSign (line 222) | func (m *Modifier) fieldsToSign(h *textproto.Header) []string {
    method ModStateForMsg (line 262) | func (m *Modifier) ModStateForMsg(ctx context.Context, msgMeta *module...
  function New (line 116) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type state (line 255) | type state struct
    method RewriteSender (line 270) | func (s *state) RewriteSender(ctx context.Context, mailFrom string) (s...
    method RewriteRcpt (line 275) | func (s *state) RewriteRcpt(ctx context.Context, rcptTo string) ([]str...
    method RewriteBody (line 279) | func (s *state) RewriteBody(ctx context.Context, h *textproto.Header, ...
    method Close (line 370) | func (s *state) Close() error {
  function init (line 374) | func init() {

FILE: internal/modify/dkim/dkim_test.go
  function newTestModifier (line 40) | func newTestModifier(t *testing.T, dir, keyAlgo string, domains []string...
  function signTestMsg (line 75) | func signTestMsg(t *testing.T, m *Modifier, envelopeFrom string) (textpr...
  function verifyTestMsg (line 104) | func verifyTestMsg(t *testing.T, keysPath string, expectedDomains []stri...
  function TestGenerateSignVerify (line 154) | func TestGenerateSignVerify(t *testing.T) {
  function TestFieldsToSign (line 226) | func TestFieldsToSign(t *testing.T) {

FILE: internal/modify/dkim/keys.go
  method loadOrGenerateKey (line 36) | func (m *Modifier) loadOrGenerateKey(keyPath, newKeyAlgo string) (pkey c...
  method generateAndWrite (line 98) | func (m *Modifier) generateAndWrite(keyPath, newKeyAlgo string) (crypto....
  function writeDNSRecord (line 157) | func writeDNSRecord(keyPath, dkimAlgoName string, pkey crypto.Signer) (s...

FILE: internal/modify/dkim/keys_test.go
  function TestKeyLoad_new (line 33) | func TestKeyLoad_new(t *testing.T) {
  constant pkeyEd25519 (line 73) | pkeyEd25519 = `-----BEGIN PRIVATE KEY-----
  constant pubkeyEd25519 (line 77) | pubkeyEd25519 = `5TPcCxzVByMyRsMFs5Dx23pnxKilI+1UrGg0t+O2oZU=`
  function TestKeyLoad_existing_pkcs8 (line 79) | func TestKeyLoad_existing_pkcs8(t *testing.T) {
  constant pkeyRSA (line 103) | pkeyRSA = `-----BEGIN RSA PRIVATE KEY-----
  function TestKeyLoad_existing_pkcs1 (line 131) | func TestKeyLoad_existing_pkcs1(t *testing.T) {

FILE: internal/modify/group.go
  type Group (line 39) | type Group struct
    method Configure (line 49) | func (g *Group) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 62) | func (g *Group) Name() string {
    method InstanceName (line 66) | func (g *Group) InstanceName() string {
    method ModStateForMsg (line 70) | func (g *Group) ModStateForMsg(ctx context.Context, msgMeta *module.Ms...
  type groupState (line 44) | type groupState struct
    method RewriteSender (line 88) | func (gs groupState) RewriteSender(ctx context.Context, mailFrom strin...
    method RewriteRcpt (line 99) | func (gs groupState) RewriteRcpt(ctx context.Context, rcptTo string) (...
    method RewriteBody (line 117) | func (gs groupState) RewriteBody(ctx context.Context, h *textproto.Hea...
    method Close (line 126) | func (gs groupState) Close() error {
  function init (line 139) | func init() {

FILE: internal/modify/replace_addr.go
  type replaceAddr (line 41) | type replaceAddr struct
    method Configure (line 61) | func (r *replaceAddr) Configure(inlineArgs []string, cfg *config.Map) ...
    method Name (line 65) | func (r *replaceAddr) Name() string {
    method InstanceName (line 69) | func (r *replaceAddr) InstanceName() string {
    method ModStateForMsg (line 73) | func (r *replaceAddr) ModStateForMsg(ctx context.Context, msgMeta *mod...
    method RewriteSender (line 77) | func (r *replaceAddr) RewriteSender(ctx context.Context, mailFrom stri...
    method RewriteRcpt (line 88) | func (r *replaceAddr) RewriteRcpt(ctx context.Context, rcptTo string) ...
    method RewriteBody (line 95) | func (r *replaceAddr) RewriteBody(ctx context.Context, h *textproto.He...
    method Close (line 99) | func (r *replaceAddr) Close() error {
    method rewrite (line 103) | func (r *replaceAddr) rewrite(ctx context.Context, val string) ([]stri...
  function NewReplaceAddr (line 50) | func NewReplaceAddr(c *container.C, modName, instName string) (module.Mo...
  function init (line 153) | func init() {

FILE: internal/modify/replace_addr_test.go
  function testReplaceAddr (line 31) | func testReplaceAddr(t *testing.T, modName string) {
  function TestReplaceAddr_RewriteSender (line 109) | func TestReplaceAddr_RewriteSender(t *testing.T) {
  function TestReplaceAddr_RewriteRcpt (line 113) | func TestReplaceAddr_RewriteRcpt(t *testing.T) {

FILE: internal/msgpipeline/bench_test.go
  function BenchmarkMsgPipelineSimple (line 29) | func BenchmarkMsgPipelineSimple(b *testing.B) {
  function BenchmarkMsgPipelineGlobalChecks (line 44) | func BenchmarkMsgPipelineGlobalChecks(b *testing.B) {
  function BenchmarkMsgPipelineTargets (line 73) | func BenchmarkMsgPipelineTargets(b *testing.B) {

FILE: internal/msgpipeline/bodynonatomic_test.go
  type multipleErrs (line 30) | type multipleErrs
    method SetStatus (line 32) | func (m multipleErrs) SetStatus(rcptTo string, err error) {
  function TestMsgPipeline_BodyNonAtomic (line 36) | func TestMsgPipeline_BodyNonAtomic(t *testing.T) {
  function TestMsgPipeline_BodyNonAtomic_ModifiedRcpt (line 68) | func TestMsgPipeline_BodyNonAtomic_ModifiedRcpt(t *testing.T) {
  function TestMsgPipeline_BodyNonAtomic_ExpandAtomic (line 110) | func TestMsgPipeline_BodyNonAtomic_ExpandAtomic(t *testing.T) {

FILE: internal/msgpipeline/check_group.go
  type CheckGroup (line 37) | type CheckGroup struct
    method Configure (line 42) | func (cg *CheckGroup) Configure(inlineArgs []string, cfg *config.Map) ...
    method Name (line 55) | func (*CheckGroup) Name() string {
    method InstanceName (line 59) | func (cg *CheckGroup) InstanceName() string {
  function init (line 63) | func init() {

FILE: internal/msgpipeline/check_runner.go
  type checkRunner (line 38) | type checkRunner struct
    method checkStates (line 70) | func (cr *checkRunner) checkStates(ctx context.Context, checks []modul...
    method runAndMergeResults (line 163) | func (cr *checkRunner) runAndMergeResults(states []module.CheckState, ...
    method checkConnSender (line 241) | func (cr *checkRunner) checkConnSender(ctx context.Context, checks []m...
    method checkRcpt (line 250) | func (cr *checkRunner) checkRcpt(ctx context.Context, checks []module....
    method checkBody (line 276) | func (cr *checkRunner) checkBody(ctx context.Context, checks []module....
    method applyResults (line 293) | func (cr *checkRunner) applyResults(hostname string, header *textproto...
    method close (line 346) | func (cr *checkRunner) close() {
  function newCheckRunner (line 59) | func newCheckRunner(msgMeta *module.MsgMetadata, log *log.Logger, r dns....

FILE: internal/msgpipeline/check_test.go
  function TestMsgPipeline_Checks (line 31) | func TestMsgPipeline_Checks(t *testing.T) {
  function TestMsgPipeline_AuthResults (line 62) | func TestMsgPipeline_AuthResults(t *testing.T) {
  function TestMsgPipeline_Headers (line 145) | func TestMsgPipeline_Headers(t *testing.T) {
  function TestMsgPipeline_Globalcheck_Errors (line 194) | func TestMsgPipeline_Globalcheck_Errors(t *testing.T) {
  function TestMsgPipeline_SourceCheck_Errors (line 272) | func TestMsgPipeline_SourceCheck_Errors(t *testing.T) {
  function TestMsgPipeline_RcptCheck_Errors (line 353) | func TestMsgPipeline_RcptCheck_Errors(t *testing.T) {

FILE: internal/msgpipeline/config.go
  type sourceIn (line 35) | type sourceIn struct
  type msgpipelineCfg (line 40) | type msgpipelineCfg struct
  function parseMsgPipelineRootCfg (line 49) | func parseMsgPipelineRootCfg(globals map[string]interface{}, nodes []con...
  function parseMsgPipelineSrcCfg (line 160) | func parseMsgPipelineSrcCfg(globals map[string]interface{}, nodes []conf...
  function parseMsgPipelineRcptCfg (line 258) | func parseMsgPipelineRcptCfg(globals map[string]interface{}, nodes []con...
  function parseRejectDirective (line 318) | func parseRejectDirective(node config.Node) (*exterrors.SMTPError, error) {
  function parseEnhancedCode (line 359) | func parseEnhancedCode(s string) (exterrors.EnhancedCode, error) {
  function parseChecksGroup (line 376) | func parseChecksGroup(globals map[string]interface{}, node config.Node) ...
  function parseModifiersGroup (line 385) | func parseModifiersGroup(globals map[string]interface{}, node config.Nod...
  function validMatchRule (line 395) | func validMatchRule(rule string) bool {

FILE: internal/msgpipeline/config_test.go
  function policyError (line 30) | func policyError(code int) error {
  function TestMsgPipelineCfg (line 39) | func TestMsgPipelineCfg(t *testing.T) {
  function TestMsgPipelineCfg_SourceIn (line 249) | func TestMsgPipelineCfg_SourceIn(t *testing.T) {
  function TestMsgPipelineCfg_DestIn (line 270) | func TestMsgPipelineCfg_DestIn(t *testing.T) {
  function TestMsgPipelineCfg_GlobalChecks (line 291) | func TestMsgPipelineCfg_GlobalChecks(t *testing.T) {
  function TestMsgPipelineCfg_GlobalChecksMultiple (line 312) | func TestMsgPipelineCfg_GlobalChecksMultiple(t *testing.T) {
  function TestMsgPipelineCfg_SourceChecks (line 336) | func TestMsgPipelineCfg_SourceChecks(t *testing.T) {
  function TestMsgPipelineCfg_SourceChecks_Multiple (line 361) | func TestMsgPipelineCfg_SourceChecks_Multiple(t *testing.T) {
  function TestMsgPipelineCfg_RcptChecks (line 389) | func TestMsgPipelineCfg_RcptChecks(t *testing.T) {
  function TestMsgPipelineCfg_RcptChecks_Multiple (line 414) | func TestMsgPipelineCfg_RcptChecks_Multiple(t *testing.T) {

FILE: internal/msgpipeline/dmarc_test.go
  function doTestDelivery (line 41) | func doTestDelivery(t *testing.T, tgt module.DeliveryTarget, from string...
  function dmarcResult (line 83) | func dmarcResult(t *testing.T, hdr textproto.Header) authres.ResultValue {
  function TestDMARC (line 105) | func TestDMARC(t *testing.T) {

FILE: internal/msgpipeline/metrics.go
  function init (line 44) | func init() {

FILE: internal/msgpipeline/modifier_test.go
  function TestMsgPipeline_SenderModifier (line 30) | func TestMsgPipeline_SenderModifier(t *testing.T) {
  function TestMsgPipeline_SenderModifier_Multiple (line 67) | func TestMsgPipeline_SenderModifier_Multiple(t *testing.T) {
  function TestMsgPipeline_SenderModifier_PreDispatch (line 109) | func TestMsgPipeline_SenderModifier_PreDispatch(t *testing.T) {
  function TestMsgPipeline_SenderModifier_PostDispatch (line 147) | func TestMsgPipeline_SenderModifier_PostDispatch(t *testing.T) {
  function TestMsgPipeline_SenderModifier_PerRcpt (line 185) | func TestMsgPipeline_SenderModifier_PerRcpt(t *testing.T) {
  function TestMsgPipeline_RcptModifier (line 233) | func TestMsgPipeline_RcptModifier(t *testing.T) {
  function TestMsgPipeline_RcptModifier_OriginalRcpt (line 271) | func TestMsgPipeline_RcptModifier_OriginalRcpt(t *testing.T) {
  function TestMsgPipeline_RcptModifier_OriginalRcpt_Multiple (line 317) | func TestMsgPipeline_RcptModifier_OriginalRcpt_Multiple(t *testing.T) {
  function TestMsgPipeline_RcptModifier_Multiple (line 372) | func TestMsgPipeline_RcptModifier_Multiple(t *testing.T) {
  function TestMsgPipeline_RcptModifier_PreDispatch (line 416) | func TestMsgPipeline_RcptModifier_PreDispatch(t *testing.T) {
  function TestMsgPipeline_RcptModifier_PostDispatch (line 468) | func TestMsgPipeline_RcptModifier_PostDispatch(t *testing.T) {
  function TestMsgPipeline_GlobalModifier_Errors (line 513) | func TestMsgPipeline_GlobalModifier_Errors(t *testing.T) {
  function TestMsgPipeline_SourceModifier_Errors (line 580) | func TestMsgPipeline_SourceModifier_Errors(t *testing.T) {
  function TestMsgPipeline_RcptModifier_Errors (line 651) | func TestMsgPipeline_RcptModifier_Errors(t *testing.T) {

FILE: internal/msgpipeline/module.go
  type Module (line 29) | type Module struct
    method Configure (line 42) | func (m *Module) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 62) | func (m *Module) Name() string {
    method InstanceName (line 66) | func (m *Module) InstanceName() string {
  function NewModule (line 35) | func NewModule(c *container.C, modName, instName string) (module.Module,...
  function init (line 70) | func init() {

FILE: internal/msgpipeline/msgpipeline.go
  type MsgPipeline (line 45) | type MsgPipeline struct
    method RunEarlyChecks (line 96) | func (d *MsgPipeline) RunEarlyChecks(ctx context.Context, state *modul...
    method StartDelivery (line 120) | func (d *MsgPipeline) StartDelivery(ctx context.Context, msgMeta *modu...
  type rcptIn (line 67) | type rcptIn struct
  type sourceBlock (line 72) | type sourceBlock struct
  type rcptBlock (line 81) | type rcptBlock struct
  function New (line 88) | func New(globals map[string]interface{}, cfg []config.Node) (*MsgPipelin...
  type delivery (line 259) | type delivery struct
  type msgpipelineDelivery (line 265) | type msgpipelineDelivery struct
    method start (line 143) | func (dd *msgpipelineDelivery) start(ctx context.Context, msgMeta *mod...
    method initRunGlobalModifiers (line 182) | func (dd *msgpipelineDelivery) initRunGlobalModifiers(ctx context.Cont...
    method srcBlockForAddr (line 198) | func (dd *msgpipelineDelivery) srcBlockForAddr(ctx context.Context, ma...
    method AddRcpt (line 282) | func (dd *msgpipelineDelivery) AddRcpt(ctx context.Context, to string,...
    method Body (line 382) | func (dd *msgpipelineDelivery) Body(ctx context.Context, header textpr...
    method BodyNonAtomic (line 455) | func (dd *msgpipelineDelivery) BodyNonAtomic(ctx context.Context, c mo...
    method Commit (line 508) | func (dd *msgpipelineDelivery) Commit(ctx context.Context) error {
    method close (line 520) | func (dd *msgpipelineDelivery) close() {
    method Abort (line 540) | func (dd *msgpipelineDelivery) Abort(ctx context.Context) error {
    method rcptBlockForAddr (line 554) | func (dd *msgpipelineDelivery) rcptBlockForAddr(ctx context.Context, r...
    method getRcptModifiers (line 608) | func (dd *msgpipelineDelivery) getRcptModifiers(ctx context.Context, r...
    method getDelivery (line 629) | func (dd *msgpipelineDelivery) getDelivery(ctx context.Context, tgt mo...
  type statusCollector (line 442) | type statusCollector struct
    method SetStatus (line 447) | func (sc statusCollector) SetStatus(rcptTo string, err error) {
  function Mock (line 652) | func Mock(tgt module.DeliveryTarget, globalChecks []module.Check) *MsgPi...

FILE: internal/msgpipeline/msgpipeline_test.go
  function TestMsgPipeline_AllToTarget (line 34) | func TestMsgPipeline_AllToTarget(t *testing.T) {
  function TestMsgPipeline_PerSourceDomainSplit (line 58) | func TestMsgPipeline_PerSourceDomainSplit(t *testing.T) {
  function TestMsgPipeline_SourceIn (line 95) | func TestMsgPipeline_SourceIn(t *testing.T) {
  function TestMsgPipeline_EmptyMAILFROM (line 149) | func TestMsgPipeline_EmptyMAILFROM(t *testing.T) {
  function TestMsgPipeline_EmptyMAILFROM_ExplicitDest (line 172) | func TestMsgPipeline_EmptyMAILFROM_ExplicitDest(t *testing.T) {
  function TestMsgPipeline_PerRcptAddrSplit (line 197) | func TestMsgPipeline_PerRcptAddrSplit(t *testing.T) {
  function TestMsgPipeline_PerRcptDomainSplit (line 233) | func TestMsgPipeline_PerRcptDomainSplit(t *testing.T) {
  function TestMsgPipeline_DestInSplit (line 271) | func TestMsgPipeline_DestInSplit(t *testing.T) {
  function TestMsgPipeline_PerSourceAddrAndDomainSplit (line 323) | func TestMsgPipeline_PerSourceAddrAndDomainSplit(t *testing.T) {
  function TestMsgPipeline_PerSourceReject (line 360) | func TestMsgPipeline_PerSourceReject(t *testing.T) {
  function TestMsgPipeline_PerRcptReject (line 394) | func TestMsgPipeline_PerRcptReject(t *testing.T) {
  function TestMsgPipeline_PostmasterRcpt (line 440) | func TestMsgPipeline_PostmasterRcpt(t *testing.T) {
  function TestMsgPipeline_PostmasterSrc (line 469) | func TestMsgPipeline_PostmasterSrc(t *testing.T) {
  function TestMsgPipeline_CaseInsensetiveMatch_Src (line 498) | func TestMsgPipeline_CaseInsensetiveMatch_Src(t *testing.T) {
  function TestMsgPipeline_CaseInsensetiveMatch_Rcpt (line 540) | func TestMsgPipeline_CaseInsensetiveMatch_Rcpt(t *testing.T) {
  function TestMsgPipeline_UnicodeNFC_Rcpt (line 576) | func TestMsgPipeline_UnicodeNFC_Rcpt(t *testing.T) {
  function TestMsgPipeline_MalformedSource (line 607) | func TestMsgPipeline_MalformedSource(t *testing.T) {
  function TestMsgPipeline_TwoRcptToOneTarget (line 638) | func TestMsgPipeline_TwoRcptToOneTarget(t *testing.T) {
  function TestMsgPipeline_multi_alias (line 665) | func TestMsgPipeline_multi_alias(t *testing.T) {

FILE: internal/msgpipeline/objname.go
  function objectName (line 29) | func objectName(x interface{}) string {

FILE: internal/msgpipeline/regress_test.go
  function TestMsgPipeline_Issue161 (line 28) | func TestMsgPipeline_Issue161(t *testing.T) {
  function TestMsgPipeline_Issue161_2 (line 66) | func TestMsgPipeline_Issue161_2(t *testing.T) {
  function TestMsgPipeline_Issue161_3 (line 102) | func TestMsgPipeline_Issue161_3(t *testing.T) {

FILE: internal/proxy_protocol/proxy_protocol.go
  type ProxyProtocol (line 14) | type ProxyProtocol struct
  function ProxyProtocolDirective (line 19) | func ProxyProtocolDirective(_ *config.Map, node config.Node) (interface{...
  function NewListener (line 53) | func NewListener(inner net.Listener, p *ProxyProtocol, logger *log.Logge...

FILE: internal/smtpconn/pool/pool.go
  type Conn (line 29) | type Conn interface
  type Config (line 35) | type Config struct
  type slot (line 43) | type slot struct
  type P (line 49) | type P struct
    method cleanUpTick (line 75) | func (p *P) cleanUpTick(stop chan struct{}) {
    method CleanUp (line 90) | func (p *P) CleanUp(ctx context.Context) {
    method close (line 107) | func (p *P) close(c Conn) {
    method Get (line 113) | func (p *P) Get(ctx context.Context, key string) (Conn, error) {
    method Return (line 168) | func (p *P) Return(key string, c Conn) {
    method Close (line 209) | func (p *P) Close() {
  function New (line 57) | func New(cfg Config) *P {

FILE: internal/smtpconn/smtpconn.go
  type C (line 52) | type C struct
    method wrapClientErr (line 102) | func (c *C) wrapClientErr(err error, serverName string) error {
    method Connect (line 166) | func (c *C) Connect(ctx context.Context, endp config.Endpoint, starttl...
    method ConnectLMTP (line 184) | func (c *C) ConnectLMTP(ctx context.Context, endp config.Endpoint, sta...
    method LocalAddr (line 217) | func (c *C) LocalAddr() net.Addr {
    method RemoteAddr (line 224) | func (c *C) RemoteAddr() net.Addr {
    method closeClient (line 231) | func (c *C) closeClient(cl *smtp.Client) {
    method attemptConnect (line 237) | func (c *C) attemptConnect(ctx context.Context, lmtp bool, endp config...
    method Mail (line 314) | func (c *C) Mail(ctx context.Context, from string, opts smtp.MailOptio...
    method Rcpts (line 358) | func (c *C) Rcpts() []string {
    method ServerName (line 362) | func (c *C) ServerName() string {
    method Client (line 366) | func (c *C) Client() *smtp.Client {
    method IsLMTP (line 370) | func (c *C) IsLMTP() bool {
    method Rcpt (line 378) | func (c *C) Rcpt(ctx context.Context, to string, opts smtp.RcptOptions...
    method smtpToLMTPData (line 448) | func (c *C) smtpToLMTPData(ctx context.Context, hdr textproto.Header, ...
    method Data (line 470) | func (c *C) Data(ctx context.Context, hdr textproto.Header, body io.Re...
    method LMTPData (line 497) | func (c *C) LMTPData(ctx context.Context, hdr textproto.Header, body i...
    method Noop (line 520) | func (c *C) Noop() error {
    method Close (line 530) | func (c *C) Close() error {
    method DirectClose (line 559) | func (c *C) DirectClose() error {
  function New (line 91) | func New() *C {
  type TLSError (line 205) | type TLSError struct
    method Error (line 209) | func (err TLSError) Error() string {
    method Unwrap (line 213) | func (err TLSError) Unwrap() error {
  type lmtpError (line 411) | type lmtpError
    method SetStatus (line 413) | func (l lmtpError) SetStatus(rcptTo string, err *smtp.SMTPError) {
    method singleError (line 417) | func (l lmtpError) singleError() *smtp.SMTPError {
    method Unwrap (line 434) | func (l lmtpError) Unwrap() error {
    method Error (line 441) | func (l lmtpError) Error() string {

FILE: internal/smtpconn/smtpconn_test.go
  function TestMain (line 31) | func TestMain(m *testing.M) {

FILE: internal/smtpconn/smtputf8_test.go
  function doTestDelivery (line 34) | func doTestDelivery(t *testing.T, conn *C, from string, to []string, opt...
  function TestSMTPUTF8 (line 52) | func TestSMTPUTF8(t *testing.T) {

FILE: internal/sqlite/is.go
  function IsSqliteDriver (line 21) | func IsSqliteDriver(name string) bool {

FILE: internal/sqlite/modernc_sqlite3.go
  constant IsAvailable (line 26) | IsAvailable  = true
  constant IsTranspiled (line 27) | IsTranspiled = true
  function MapDriverName (line 30) | func MapDriverName(n string) string {

FILE: internal/sqlite/no_sqlite3.go
  constant IsAvailable (line 24) | IsAvailable  = false
  constant IsTranspiled (line 25) | IsTranspiled = false
  function MapDriverName (line 28) | func MapDriverName(n string) string {

FILE: internal/sqlite/sqlite3.go
  constant IsAvailable (line 26) | IsAvailable  = true
  constant IsTranspiled (line 27) | IsTranspiled = false
  function MapDriverName (line 30) | func MapDriverName(n string) string {

FILE: internal/storage/blob/fs/fs.go
  type FSStore (line 17) | type FSStore struct
    method Name (line 26) | func (s *FSStore) Name() string {
    method InstanceName (line 30) | func (s *FSStore) InstanceName() string {
    method Configure (line 34) | func (s *FSStore) Configure(inlineArgs []string, cfg *config.Map) error {
    method Open (line 59) | func (s *FSStore) Open(_ context.Context, key string) (io.ReadCloser, ...
    method Create (line 70) | func (s *FSStore) Create(_ context.Context, key string, blobSize int64...
    method Delete (line 83) | func (s *FSStore) Delete(_ context.Context, keys []string) error {
  function New (line 22) | func New(_ *container.C, _, instName string) (module.Module, error) {
  function init (line 95) | func init() {

FILE: internal/storage/blob/fs/fs_test.go
  function TestFS (line 13) | func TestFS(t *testing.T) {

FILE: internal/storage/blob/s3/s3.go
  constant modName (line 18) | modName = "storage.blob.s3"
  constant credsTypeFileMinio (line 21) | credsTypeFileMinio = "file_minio"
  constant credsTypeFileAWS (line 22) | credsTypeFileAWS   = "file_aws"
  constant credsTypeAccessKey (line 23) | credsTypeAccessKey = "access_key"
  constant credsTypeIAM (line 24) | credsTypeIAM       = "iam"
  constant credsTypeDefault (line 25) | credsTypeDefault   = credsTypeAccessKey
  type Store (line 28) | type Store struct
    method Configure (line 46) | func (s *Store) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 102) | func (s *Store) Name() string {
    method InstanceName (line 106) | func (s *Store) InstanceName() string {
    method Create (line 145) | func (s *Store) Create(ctx context.Context, key string, blobSize int64...
    method Open (line 174) | func (s *Store) Open(ctx context.Context, key string) (io.ReadCloser, ...
    method Delete (line 186) | func (s *Store) Delete(ctx context.Context, keys []string) error {
  function New (line 39) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type s3blob (line 110) | type s3blob struct
    method Sync (line 116) | func (b *s3blob) Sync() error {
    method Write (line 132) | func (b *s3blob) Write(p []byte) (n int, err error) {
    method Close (line 136) | func (b *s3blob) Close() error {
  function init (line 197) | func init() {

FILE: internal/storage/blob/s3/s3_test.go
  function TestFS (line 14) | func TestFS(t *testing.T) {

FILE: internal/storage/blob/test_blob.go
  type testBack (line 17) | type testBack struct
  function TestStore (line 22) | func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStor...

FILE: internal/storage/blob/test_blob_nosqlite.go
  function TestStore (line 12) | func TestStore(t *testing.T, newStore func() module.BlobStore, cleanStor...

FILE: internal/storage/imapsql/bench_test.go
  function init (line 37) | func init() {
  function createTestDB (line 43) | func createTestDB(tb testing.TB, compAlgo string) *Storage {
  function BenchmarkStorage_Delivery (line 59) | func BenchmarkStorage_Delivery(b *testing.B) {
  function BenchmarkStorage_DeliveryLZ4 (line 70) | func BenchmarkStorage_DeliveryLZ4(b *testing.B) {
  function BenchmarkStorage_DeliveryZstd (line 81) | func BenchmarkStorage_DeliveryZstd(b *testing.B) {

FILE: internal/storage/imapsql/delivery.go
  type addedRcpt (line 37) | type addedRcpt struct
  type delivery (line 40) | type delivery struct
    method String (line 49) | func (d *delivery) String() string {
    method AddRcpt (line 63) | func (d *delivery) AddRcpt(ctx context.Context, rcptTo string, _ smtp....
    method Body (line 104) | func (d *delivery) Body(ctx context.Context, header textproto.Header, ...
    method Abort (line 150) | func (d *delivery) Abort(ctx context.Context) error {
    method Commit (line 156) | func (d *delivery) Commit(ctx context.Context) error {
  function userDoesNotExist (line 53) | func userDoesNotExist(actual error) error {
  method StartDelivery (line 162) | func (store *Storage) StartDelivery(ctx context.Context, msgMeta *module...

FILE: internal/storage/imapsql/external_blob_store.go
  type ExtBlob (line 11) | type ExtBlob struct
    method Sync (line 15) | func (e ExtBlob) Sync() error {
    method Write (line 19) | func (e ExtBlob) Write(p []byte) (n int, err error) {
  type WriteExtBlob (line 23) | type WriteExtBlob struct
    method Read (line 27) | func (w WriteExtBlob) Read(p []byte) (n int, err error) {
  type ExtBlobStore (line 31) | type ExtBlobStore struct
    method Create (line 35) | func (e ExtBlobStore) Create(key string, objSize int64) (imapsql.ExtSt...
    method Open (line 47) | func (e ExtBlobStore) Open(key string) (imapsql.ExtStoreObj, error) {
    method Delete (line 59) | func (e ExtBlobStore) Delete(keys []string) error {

FILE: internal/storage/imapsql/imapsql.go
  constant modName (line 61) | modName = "storage.imapsql"
  type Storage (line 63) | type Storage struct
    method Name (line 89) | func (store *Storage) Name() string {
    method InstanceName (line 93) | func (store *Storage) InstanceName() string {
    method Configure (line 106) | func (store *Storage) Configure(inlineArgs []string, cfg *config.Map) ...
    method Start (line 293) | func (store *Storage) Start() error {
    method EnableUpdatePipe (line 303) | func (store *Storage) EnableUpdatePipe(mode updatepipe.BackendMode) er...
    method I18NLevel (line 392) | func (store *Storage) I18NLevel() int {
    method IMAPExtensions (line 396) | func (store *Storage) IMAPExtensions() []string {
    method CreateMessageLimit (line 400) | func (store *Storage) CreateMessageLimit() *uint32 {
    method GetOrCreateIMAPAcct (line 404) | func (store *Storage) GetOrCreateIMAPAcct(username string) (backend.Us...
    method Lookup (line 413) | func (store *Storage) Lookup(ctx context.Context, key string) (string,...
    method Stop (line 433) | func (store *Storage) Stop() error {
    method Login (line 454) | func (store *Storage) Login(_ *imap.ConnInfo, usenrame, password strin...
    method SupportedThreadAlgorithms (line 458) | func (store *Storage) SupportedThreadAlgorithms() []sortthread.ThreadA...
  function New (line 97) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function init (line 462) | func init() {

FILE: internal/storage/imapsql/maddyctl.go
  method ListIMAPAccts (line 28) | func (store *Storage) ListIMAPAccts() ([]string, error) {
  method CreateIMAPAcct (line 32) | func (store *Storage) CreateIMAPAcct(accountName string) error {
  method DeleteIMAPAcct (line 36) | func (store *Storage) DeleteIMAPAcct(accountName string) error {
  method GetIMAPAcct (line 40) | func (store *Storage) GetIMAPAcct(accountName string) (backend.User, err...

FILE: internal/table/chain.go
  type Chain (line 31) | type Chain struct
    method Configure (line 46) | func (s *Chain) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 74) | func (s *Chain) Name() string {
    method InstanceName (line 78) | func (s *Chain) InstanceName() string {
    method Lookup (line 82) | func (s *Chain) Lookup(ctx context.Context, key string) (string, bool,...
    method LookupMulti (line 94) | func (s *Chain) LookupMulti(ctx context.Context, key string) ([]string...
  function NewChain (line 39) | func NewChain(_ *container.C, modName, instName string) (module.Module, ...
  function init (line 131) | func init() {

FILE: internal/table/email_localpart.go
  type EmailLocalpart (line 31) | type EmailLocalpart struct
    method Configure (line 45) | func (s *EmailLocalpart) Configure(inlineArgs []string, cfg *config.Ma...
    method Name (line 49) | func (s *EmailLocalpart) Name() string {
    method InstanceName (line 53) | func (s *EmailLocalpart) InstanceName() string {
    method Lookup (line 57) | func (s *EmailLocalpart) Lookup(ctx context.Context, key string) (stri...
  function NewEmailLocalpart (line 37) | func NewEmailLocalpart(_ *container.C, modName, instName string) (module...
  function init (line 69) | func init() {

FILE: internal/table/email_with_domain.go
  type EmailWithDomain (line 33) | type EmailWithDomain struct
    method Configure (line 48) | func (s *EmailWithDomain) Configure(inlineArgs []string, cfg *config.M...
    method Name (line 63) | func (s *EmailWithDomain) Name() string {
    method InstanceName (line 67) | func (s *EmailWithDomain) InstanceName() string {
    method Lookup (line 71) | func (s *EmailWithDomain) Lookup(ctx context.Context, key string) (str...
    method LookupMulti (line 81) | func (s *EmailWithDomain) LookupMulti(ctx context.Context, key string)...
  function NewEmailWithDomain (line 40) | func NewEmailWithDomain(c *container.C, modName, instName string) (modul...
  function init (line 90) | func init() {

FILE: internal/table/file.go
  constant FileModName (line 38) | FileModName = "table.file"
  type File (line 40) | type File struct
    method Name (line 66) | func (f *File) Name() string {
    method InstanceName (line 70) | func (f *File) InstanceName() string {
    method Configure (line 74) | func (f *File) Configure(inlineArgs []string, cfg *config.Map) error {
    method Start (line 107) | func (f *File) Start() error {
    method Reload (line 112) | func (f *File) Reload() error {
    method Stop (line 117) | func (f *File) Stop() error {
    method reloader (line 125) | func (f *File) reloader() {
    method reload (line 151) | func (f *File) reload() {
    method Lookup (line 238) | func (f *File) Lookup(_ context.Context, val string) (string, bool, er...
    method LookupMulti (line 254) | func (f *File) LookupMulti(_ context.Context, val string) ([]string, e...
  function NewFile (line 54) | func NewFile(c *container.C, modName, instName string) (module.Module, e...
  function readFile (line 196) | func readFile(path string, out map[string][]string) error {
  function init (line 262) | func init() {

FILE: internal/table/file_test.go
  function TestReadFile (line 34) | func TestReadFile(t *testing.T) {
  function TestFileReload (line 98) | func TestFileReload(t *testing.T) {
  function TestFileReload_Broken (line 162) | func TestFileReload_Broken(t *testing.T) {
  function TestFileReload_Removed (line 226) | func TestFileReload_Removed(t *testing.T) {
  function init (line 278) | func init() {

FILE: internal/table/identity.go
  type Identity (line 30) | type Identity struct
    method Configure (line 42) | func (s *Identity) Configure(inlineArgs []string, cfg *config.Map) err...
    method Name (line 46) | func (s *Identity) Name() string {
    method InstanceName (line 50) | func (s *Identity) InstanceName() string {
    method Lookup (line 54) | func (s *Identity) Lookup(_ context.Context, key string) (string, bool...
  function NewIdentity (line 35) | func NewIdentity(_ *container.C, modName, instName string) (module.Modul...
  function init (line 58) | func init() {

FILE: internal/table/regexp.go
  type Regexp (line 33) | type Regexp struct
    method Configure (line 50) | func (r *Regexp) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 88) | func (r *Regexp) Name() string {
    method InstanceName (line 92) | func (r *Regexp) InstanceName() string {
    method LookupMulti (line 96) | func (r *Regexp) LookupMulti(_ context.Context, key string) ([]string,...
    method Lookup (line 113) | func (r *Regexp) Lookup(ctx context.Context, key string) (string, bool...
  function NewRegexp (line 43) | func NewRegexp(_ *container.C, modName, instName string) (module.Module,...
  function init (line 125) | func init() {

FILE: internal/table/sql_query.go
  type SQL (line 37) | type SQL struct
    method Name (line 59) | func (s *SQL) Name() string {
    method InstanceName (line 63) | func (s *SQL) InstanceName() string {
    method Configure (line 67) | func (s *SQL) Configure(inlineArgs []string, cfg *config.Map) error {
    method Start (line 145) | func (s *SQL) Start() error {
    method Stop (line 149) | func (s *SQL) Stop() error {
    method Lookup (line 156) | func (s *SQL) Lookup(ctx context.Context, val string) (string, bool, e...
    method LookupMulti (line 175) | func (s *SQL) LookupMulti(ctx context.Context, val string) ([]string, ...
    method Keys (line 202) | func (s *SQL) Keys() ([]string, error) {
    method RemoveKey (line 228) | func (s *SQL) RemoveKey(k string) error {
    method SetKey (line 245) | func (s *SQL) SetKey(k, v string) error {
  function NewSQL (line 52) | func NewSQL(_ *container.C, modName, instName string) (module.Module, er...
  function init (line 269) | func init() {

FILE: internal/table/sql_query_test.go
  function TestSQL (line 35) | func TestSQL(t *testing.T) {

FILE: internal/table/sql_table.go
  type SQLTable (line 32) | type SQLTable struct
    method Name (line 51) | func (s *SQLTable) Name() string {
    method InstanceName (line 55) | func (s *SQLTable) InstanceName() string {
    method Configure (line 59) | func (s *SQLTable) Configure(inlineArgs []string, cfg *config.Map) err...
    method Start (line 149) | func (s *SQLTable) Start() error { return s.wrapped.Start() }
    method Stop (line 151) | func (s *SQLTable) Stop() error {
    method Lookup (line 155) | func (s *SQLTable) Lookup(ctx context.Context, val string) (string, bo...
    method LookupMulti (line 159) | func (s *SQLTable) LookupMulti(ctx context.Context, val string) ([]str...
    method Keys (line 163) | func (s *SQLTable) Keys() ([]string, error) {
    method RemoveKey (line 167) | func (s *SQLTable) RemoveKey(k string) error {
    method SetKey (line 171) | func (s *SQLTable) SetKey(k, v string) error {
  function NewSQLTable (line 39) | func NewSQLTable(_ *container.C, modName, instName string) (module.Modul...
  function init (line 175) | func init() {

FILE: internal/table/static.go
  type Static (line 30) | type Static struct
    method Configure (line 45) | func (s *Static) Configure(inlineArgs []string, cfg *config.Map) error {
    method Name (line 57) | func (s *Static) Name() string {
    method InstanceName (line 61) | func (s *Static) InstanceName() string {
    method Lookup (line 65) | func (s *Static) Lookup(ctx context.Context, key string) (string, bool...
    method LookupMulti (line 73) | func (s *Static) LookupMulti(ctx context.Context, key string) ([]strin...
  function NewStatic (line 37) | func NewStatic(_ *container.C, modName, instName string) (module.Module,...
  function init (line 77) | func init() {

FILE: internal/target/delivery.go
  function DeliveryLogger (line 26) | func DeliveryLogger(parent *log.Logger, msgMeta *module.MsgMetadata) *lo...

FILE: internal/target/queue/metrics.go
  function init (line 33) | func init() {

FILE: internal/target/queue/queue.go
  type partialError (line 93) | type partialError struct
    method SetStatus (line 104) | func (pe *partialError) SetStatus(rcptTo string, err error) {
    method Error (line 114) | func (pe *partialError) Error() string {
  type Queue (line 122) | type Queue struct
    method Configure (line 203) | func (q *Queue) Configure(inlineArgs []string, cfg *config.Map) error {
    method Start (line 252) | func (q *Queue) Start() error {
    method start (line 256) | func (q *Queue) start(maxParallelism int) error {
    method EarlyStop (line 269) | func (q *Queue) EarlyStop() error {
    method Stop (line 277) | func (q *Queue) Stop() error {
    method discardBroken (line 288) | func (q *Queue) discardBroken(id string) {
    method dispatch (line 296) | func (q *Queue) dispatch(ctx context.Context, value TimeSlot[queueSlot...
    method tryDelivery (line 386) | func (q *Queue) tryDelivery(ctx context.Context, meta *QueueMetadata, ...
    method deliver (line 473) | func (q *Queue) deliver(ctx context.Context, meta *QueueMetadata, head...
    method StartDelivery (line 642) | func (q *Queue) StartDelivery(ctx context.Context, msgMeta *module.Msg...
    method removeFromDisk (line 653) | func (q *Queue) removeFromDisk(msgMeta *module.MsgMetadata) {
    method readDiskQueue (line 678) | func (q *Queue) readDiskQueue() error {
    method storeNewMessage (line 755) | func (q *Queue) storeNewMessage(meta *QueueMetadata, header textproto....
    method updateMetadataOnDisk (line 821) | func (q *Queue) updateMetadataOnDisk(meta *QueueMetadata) error {
    method readMessageMeta (line 864) | func (q *Queue) readMessageMeta(id string) (*QueueMetadata, error) {
    method tryRemoveDanglingFile (line 897) | func (q *Queue) tryRemoveDanglingFile(name string) {
    method openMessage (line 905) | func (q *Queue) openMessage(id string) (*QueueMetadata, textproto.Head...
    method InstanceName (line 940) | func (q *Queue) InstanceName() string {
    method Name (line 944) | func (q *Queue) Name() string {
    method emitDSN (line 948) | func (q *Queue) emitDSN(meta *QueueMetadata, header textproto.Header, ...
  type QueueMetadata (line 159) | type QueueMetadata struct
  type queueSlot (line 182) | type queueSlot struct
  function New (line 192) | func New(c *container.C, modName, instName string) (module.Module, error) {
  function toSMTPErr (line 346) | func toSMTPErr(err error) *smtp.SMTPError {
  type queueDelivery (line 587) | type queueDelivery struct
    method AddRcpt (line 595) | func (qd *queueDelivery) AddRcpt(ctx context.Context, rcptTo string, _...
    method Body (line 600) | func (qd *queueDelivery) Body(ctx context.Context, header textproto.He...
    method Abort (line 615) | func (qd *queueDelivery) Abort(ctx context.Context) error {
    method Commit (line 624) | func (qd *queueDelivery) Commit(ctx context.Context) error {
  type BufferedReadCloser (line 892) | type BufferedReadCloser struct
  function init (line 1057) | func init() {

FILE: internal/target/queue/queue_test.go
  function newTestQueue (line 50) | func newTestQueue(t *testing.T, target module.DeliveryTarget) *Queue {
  function cleanQueue (line 54) | func cleanQueue(t *testing.T, q *Queue) {
  function newTestQueueDir (line 61) | func newTestQueueDir(t *testing.T, target module.DeliveryTarget, dir str...
  type unreliableTarget (line 86) | type unreliableTarget struct
    method StartDelivery (line 164) | func (ut *unreliableTarget) StartDelivery(ctx context.Context, msgMeta...
  type unreliableTargetDelivery (line 101) | type unreliableTargetDelivery struct
    method AddRcpt (line 110) | func (utd *unreliableTargetDelivery) AddRcpt(ctx context.Context, rcpt...
    method Body (line 122) | func (utd *unreliableTargetDelivery) Body(ctx context.Context, header ...
    method Abort (line 148) | func (utd *unreliableTargetDelivery) Abort(ctx context.Context) error {
    method Commit (line 156) | func (utd *unreliableTargetDelivery) Commit(ctx context.Context) error {
  type unreliableTargetDeliveryPartial (line 106) | type unreliableTargetDeliveryPartial struct
    method BodyNonAtomic (line 137) | func (utd *unreliableTargetDeliveryPartial) BodyNonAtomic(ctx context....
  function readMsgChanTimeout (line 185) | func readMsgChanTimeout(t *testing.T, ch <-chan testutils.Msg, timeout t...
  function checkQueueDir (line 197) | func checkQueueDir(t *testing.T, q *Queue, expectedIDs []string) {
  function TestQueueDelivery (line 239) | func TestQueueDelivery(t *testing.T) {
  function TestQueueDelivery_PermanentFail_NonPartial (line 258) | func TestQueueDelivery_PermanentFail_NonPartial(t *testing.T) {
  function TestQueueDelivery_PermanentFail_Partial (line 280) | func TestQueueDelivery_PermanentFail_Partial(t *testing.T) {
  function TestQueueDelivery_TemporaryFail (line 305) | func TestQueueDelivery_TemporaryFail(t *testing.T) {
  function TestQueueDelivery_TemporaryFail_Partial (line 332) | func TestQueueDelivery_TemporaryFail_Partial(t *testing.T) {
  function TestQueueDelivery_MultipleAttempts (line 365) | func TestQueueDelivery_MultipleAttempts(t *testing.T) {
  function TestQueueDelivery_PermanentRcptReject (line 405) | func TestQueueDelivery_PermanentRcptReject(t *testing.T) {
  function TestQueueDelivery_TemporaryRcptReject (line 430) | func TestQueueDelivery_TemporaryRcptReject(t *testing.T) {
  function TestQueueDelivery_SerializationRoundtrip (line 464) | func TestQueueDelivery_SerializationRoundtrip(t *testing.T) {
  function TestQueueDelivery_DeserlizationCleanUp (line 511) | func TestQueueDelivery_DeserlizationCleanUp(t *testing.T) {
  function TestQueueDelivery_AbortIfNoRecipients (line 566) | func TestQueueDelivery_AbortIfNoRecipients(t *testing.T) {
  function TestQueueDelivery_AbortNoDangling (line 586) | func TestQueueDelivery_AbortNoDangling(t *testing.T) {
  function TestQueueDSN (line 630) | func TestQueueDSN(t *testing.T) {
  function TestQueueDSN_FromEmptyAddr (line 670) | func TestQueueDSN_FromEmptyAddr(t *testing.T) {
  function TestQueueDSN_NoDSNforDSN (line 708) | func TestQueueDSN_NoDSNforDSN(t *testing.T) {
  function TestQueueDSN_RcptRewrite (line 754) | func TestQueueDSN_RcptRewrite(t *testing.T) {
  function init (line 828) | func init() {

FILE: internal/target/queue/timewheel.go
  type TimeSlot (line 29) | type TimeSlot struct
  type TimeWheel (line 34) | type TimeWheel struct
  function NewTimeWheel (line 48) | func NewTimeWheel[Value any](dispatch func(context.Context, TimeSlot[Val...
  method Add (line 63) | func (tw *TimeWheel[Value]) Add(target time.Time, value Value) {
  method Close (line 76) | func (tw *TimeWheel[Value]) Close() {
  method tick (line 94) | func (tw *TimeWheel[Value]) tick(ctx context.Context) {

FILE: internal/target/queue/timewheel_test.go
  function TestTimeWheelAdd (line 27) | func TestTimeWheelAdd(t *testing.T) {
  function TestTimeWheelAdd_Ordering (line 45) | func TestTimeWheelAdd_Ordering(t *testing.T) {
  function TestTimeWheelAdd_Restart (line 68) | func TestTimeWheelAdd_Restart(t *testing.T) {
  function TestTimeWheelAdd_MissingGotoBug (line 91) | func TestTimeWheelAdd_MissingGotoBug(t *testing.T) {
  function TestTimeWheelAdd_EmptyUpdWait (line 110) | func TestTimeWheelAdd_EmptyUpdWait(t *testing.T) {

FILE: internal/target/received.go
  function SanitizeForHeader (line 33) | func SanitizeForHeader(raw string) string {
  function GenerateReceived (line 37) | func GenerateReceived(ctx context.Context, msgMeta *module.MsgMetadata, ...

FILE: internal/target/remote/connect.go
  type mxConn (line 37) | type mxConn struct
    method Usable (line 58) | func (c *mxConn) Usable() bool {
    method LastUseAt (line 65) | func (c *mxConn) LastUseAt() time.Time {
    method Close (line 69) | func (c *mxConn) Close() error {
  function isVerifyError (line 73) | func isVerifyError(err error) bool {
  method connect (line 85) | func (rd *remoteDelivery) connect(ctx context.Context, conn mxConn, host...
  method attemptMX (line 159) | func (rd *remoteDelivery) attemptMX(ctx context.Context, conn *mxConn, r...
  method closeConn (line 209) | func (rd *remoteDelivery) closeConn(c *mxConn) {
  method connectionForDomain (line 215) | func (rd *remoteDelivery) connectionForDomain(ctx context.Context, domai...
  method newConn (line 296) | func (rd *remoteDelivery) newConn(ctx context.Context, domain string) (*...
  method lookupMX (line 369) | func (rd *remoteDelivery) lookupMX(ctx context.Context, domain string) (...

FILE: internal/target/remote/dane.go
  function verifyDANE (line 40) | func verifyDANE(recs []dns.TLSA, connState tls.ConnectionState) (overrid...

FILE: internal/target/remote/dane_delivery_test.go
  function targetWithExtResolver (line 36) | func targetWithExtResolver(t *testing.T, zones map[string]mockdns.Zone) ...
  function tlsaRecord (line 61) | func tlsaRecord(name string, usage, matchType, selector uint8, cert stri...
  function TestRemoteDelivery_DANE_Ok (line 80) | func TestRemoteDelivery_DANE_Ok(t *testing.T) {
  function TestRemoteDelivery_DANE_CNAMEd_1 (line 118) | func TestRemoteDelivery_DANE_CNAMEd_1(t *testing.T) {
  function TestRemoteDelivery_DANE_CNAMEd_2 (line 159) | func TestRemoteDelivery_DANE_CNAMEd_2(t *testing.T) {
  function TestRemoteDelivery_DANE_InsecureCNAMEDest (line 201) | func TestRemoteDelivery_DANE_InsecureCNAMEDest(t *testing.T) {
  function TestRemoteDelivery_DANE_NonAD_TLSA_Ignore (line 250) | func TestRemoteDelivery_DANE_NonAD_TLSA_Ignore(t *testing.T) {
  function TestRemoteDelivery_DANE_NonADIgnore_CNAME (line 281) | func TestRemoteDelivery_DANE_NonADIgnore_CNAME(t *testing.T) {
  function TestRemoteDelivery_DANE_SkipAUnauth (line 316) | func TestRemoteDelivery_DANE_SkipAUnauth(t *testing.T) {
  function TestRemoteDelivery_DANE_Mismatch (line 348) | func TestRemoteDelivery_DANE_Mismatch(t *testing.T) {
  function TestRemoteDelivery_DANE_NoRecord (line 386) | func TestRemoteDelivery_DANE_NoRecord(t *testing.T) {
  function TestRemoteDelivery_DANE_LookupErr (line 413) | func TestRemoteDelivery_DANE_LookupErr(t *testing.T) {
  function TestRemoteDelivery_DANE_NoTLS (line 448) | func TestRemoteDelivery_DANE_NoTLS(t *testing.T) {
  function TestRemoteDelivery_DANE_TLSError (line 484) | func TestRemoteDelivery_DANE_TLSError(t *testing.T) {

FILE: internal/target/remote/dane_test.go
  function parsePEMCert (line 90) | func parsePEMCert(blob string) *x509.Certificate {
  function singleTlsaRecord (line 99) | func singleTlsaRecord(usage, matchType, selector uint8, cert string) dns...
  function keySHA256 (line 114) | func keySHA256(blob string) string {
  function TestVerifyDANE (line 120) | func TestVerifyDANE(t *testing.T) {

FILE: internal/target/remote/debugflags.go
  function init (line 29) | func init() {

FILE: internal/target/remote/metrics.go
  function init (line 43) | func init() {

FILE: internal/target/remote/mxauth_test.go
  function TestRemoteDelivery_AuthMX_MTASTS (line 39) | func TestRemoteDelivery_AuthMX_MTASTS(t *testing.T) {
  function TestRemoteDelivery_MTASTS_SkipNonMatching (line 78) | func TestRemoteDelivery_MTASTS_SkipNonMatching(t *testing.T) {
  function TestRemoteDelivery_AuthMX_MTASTS_Fail (line 133) | func TestRemoteDelivery_AuthMX_MTASTS_Fail(t *testing.T) {
  function TestRemoteDelivery_AuthMX_MTASTS_NoTLS (line 179) | func TestRemoteDelivery_AuthMX_MTASTS_NoTLS(t *testing.T) {
  function TestRemoteDelivery_AuthMX_MTASTS_RequirePKIX (line 224) | func TestRemoteDelivery_AuthMX_MTASTS_RequirePKIX(t *testing.T) {
  function TestRemoteDelivery_AuthMX_MTASTS_NoPolicy (line 272) | func TestRemoteDelivery_AuthMX_MTASTS_NoPolicy(t *testing.T) {
  function TestRemoteDelivery_AuthMX_DNSSEC (line 322) | func TestRemoteDelivery_AuthMX_DNSSEC(t *testing.T) {
  function TestRemoteDelivery_AuthMX_DNSSEC_Fail (line 369) | func TestRemoteDelivery_AuthMX_DNSSEC_Fail(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS (line 423) | func TestRemoteDelivery_REQUIRETLS(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS_Fail (line 468) | func TestRemoteDelivery_REQUIRETLS_Fail(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS_Relaxed (line 517) | func TestRemoteDelivery_REQUIRETLS_Relaxed(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth (line 563) | func TestRemoteDelivery_REQUIRETLS_Relaxed_NoMXAuth(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS (line 608) | func TestRemoteDelivery_REQUIRETLS_Relaxed_NoTLS(t *testing.T) {
  function TestRemoteDelivery_REQUIRETLS_Relaxed_TLSFail (line 658) | func TestRemoteDelivery_REQUIRETLS_Relaxed_TLSFail(t *testing.T) {

FILE: internal/target/remote/policy_group.go
  type PolicyGroup (line 38) | type PolicyGroup struct
    method Configure (line 44) | func (pg *PolicyGroup) Configure(inlineArgs []string, cfg *config.Map)...
    method Name (line 92) | func (*PolicyGroup) Name() string {
    method InstanceName (line 96) | func (pg *PolicyGroup) InstanceName() string {
  function init (line 100) | func init() {

FILE: internal/target/remote/remote.go
  function moduleError (line 58) | func moduleError(err error) error {
  type Target (line 64) | type Target struct
    method Configure (line 102) | func (rt *Target) Configure(inlineArgs []string, cfg *config.Map) error {
    method Start (line 190) | func (rt *Target) Start() error {
    method Stop (line 194) | func (rt *Target) Stop() error {
    method Name (line 200) | func (rt *Target) Name() string {
    method InstanceName (line 204) | func (rt *Target) InstanceName() string {
    method StartDelivery (line 220) | func (rt *Target) StartDelivery(ctx context.Context, msgMeta *module.M...
  function New (line 92) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type remoteDelivery (line 208) | type remoteDelivery struct
    method AddRcpt (line 280) | func (rd *remoteDelivery) AddRcpt(ctx context.Context, to string, opts...
    method Body (line 379) | func (rd *remoteDelivery) Body(ctx context.Context, header textproto.H...
    method BodyNonAtomic (line 398) | func (rd *remoteDelivery) BodyNonAtomic(ctx context.Context, c module....
    method Abort (line 445) | func (rd *remoteDelivery) Abort(ctx context.Context) error {
    method Commit (line 449) | func (rd *remoteDelivery) Commit(ctx context.Context) error {
    method Close (line 455) | func (rd *remoteDelivery) Close() error {
  type multipleErrs (line 331) | type multipleErrs struct
    method Error (line 336) | func (m *multipleErrs) Error() string {
    method Fields (line 342) | func (m *multipleErrs) Fields() map[string]interface{} {
    method SetStatus (line 373) | func (m *multipleErrs) SetStatus(rcptTo string, err error) {
  function init (line 493) | func init() {

FILE: internal/target/remote/remote_test.go
  function testTarget (line 52) | func testTarget(t *testing.T, zones map[string]mockdns.Zone, extResolver...
  function testSTSPolicy (line 77) | func testSTSPolicy(t *testing.T, zones map[string]mockdns.Zone, mtastsGe...
  function testDANEPolicy (line 103) | func testDANEPolicy(t *testing.T, extR *dns.ExtResolver) *danePolicy {
  function TestRemoteDelivery (line 121) | func TestRemoteDelivery(t *testing.T) {
  function TestRemoteDelivery_NoMXFallback (line 145) | func TestRemoteDelivery_NoMXFallback(t *testing.T) {
  function TestRemoteDelivery_EmptySender (line 176) | func TestRemoteDelivery_EmptySender(t *testing.T) {
  function TestRemoteDelivery_IPLiteral (line 200) | func TestRemoteDelivery_IPLiteral(t *testing.T) {
  function TestRemoteDelivery_FallbackMX (line 230) | func TestRemoteDelivery_FallbackMX(t *testing.T) {
  function TestRemoteDelivery_BodyNonAtomic (line 251) | func TestRemoteDelivery_BodyNonAtomic(t *testing.T) {
  function TestRemoteDelivery_Abort (line 283) | func TestRemoteDelivery_Abort(t *testing.T) {
  function TestRemoteDelivery_CommitWithoutBody (line 317) | func TestRemoteDelivery_CommitWithoutBody(t *testing.T) {
  function TestRemoteDelivery_MAILFROMErr (line 352) | func TestRemoteDelivery_MAILFROMErr(t *testing.T) {
  function TestRemoteDelivery_NoMX (line 391) | func TestRemoteDelivery_NoMX(t *testing.T) {
  function TestRemoteDelivery_NullMX (line 422) | func TestRemoteDelivery_NullMX(t *testing.T) {
  function TestRemoteDelivery_Quarantined (line 455) | func TestRemoteDelivery_Quarantined(t *testing.T) {
  function TestRemoteDelivery_MAILFROMErr_Repeated (line 501) | func TestRemoteDelivery_MAILFROMErr_Repeated(t *testing.T) {
  function TestRemoteDelivery_RcptErr (line 543) | func TestRemoteDelivery_RcptErr(t *testing.T) {
  function TestRemoteDelivery_DownMX (line 600) | func TestRemoteDelivery_DownMX(t *testing.T) {
  function TestRemoteDelivery_AllMXDown (line 630) | func TestRemoteDelivery_AllMXDown(t *testing.T) {
  function TestRemoteDelivery_Split (line 657) | func TestRemoteDelivery_Split(t *testing.T) {
  function TestRemoteDelivery_Split_Fail (line 694) | func TestRemoteDelivery_Split_Fail(t *testing.T) {
  function TestRemoteDelivery_BodyErr (line 764) | func TestRemoteDelivery_BodyErr(t *testing.T) {
  function TestRemoteDelivery_Split_BodyErr (line 813) | func TestRemoteDelivery_Split_BodyErr(t *testing.T) {
  function TestRemoteDelivery_Split_BodyErr_NonAtomic (line 875) | func TestRemoteDelivery_Split_BodyErr_NonAtomic(t *testing.T) {
  function TestRemoteDelivery_TLSErrFallback (line 949) | func TestRemoteDelivery_TLSErrFallback(t *testing.T) {
  function TestRemoteDelivery_RequireTLS_Missing (line 980) | func TestRemoteDelivery_RequireTLS_Missing(t *testing.T) {
  function TestRemoteDelivery_RequireTLS_Present (line 1008) | func TestRemoteDelivery_RequireTLS_Present(t *testing.T) {
  function TestRemoteDelivery_RequireTLS_NoErrFallback (line 1035) | func TestRemoteDelivery_RequireTLS_NoErrFallback(t *testing.T) {
  function TestRemoteDelivery_TLS_FallbackNoVerify (line 1070) | func TestRemoteDelivery_TLS_FallbackNoVerify(t *testing.T) {
  function TestRemoteDelivery_TLS_FallbackPlaintext (line 1103) | func TestRemoteDelivery_TLS_FallbackPlaintext(t *testing.T) {
  function TestMain (line 1134) | func TestMain(m *testing.M) {
  function TestRemoteDelivery_ConnReuse (line 1146) | func TestRemoteDelivery_ConnReuse(t *testing.T) {

FILE: internal/target/remote/security.go
  type mtastsPolicy (line 42) | type mtastsPolicy struct
    method Name (line 64) | func (c *mtastsPolicy) Name() string {
    method InstanceName (line 68) | func (c *mtastsPolicy) InstanceName() string {
    method Weight (line 72) | func (c *mtastsPolicy) Weight() int {
    method Configure (line 76) | func (c *mtastsPolicy) Configure(inlineArgs []string, cfg *config.Map)...
    method Start (line 104) | func (c *mtastsPolicy) Start() error {
    method StartUpdater (line 113) | func (c *mtastsPolicy) StartUpdater() {
    method Stop (line 118) | func (c *mtastsPolicy) Stop() error {
    method updater (line 127) | func (c *mtastsPolicy) updater() {
    method StartDelivery (line 161) | func (c *mtastsPolicy) StartDelivery(msgMeta *module.MsgMetadata) modu...
  type mtastsDelivery (line 49) | type mtastsDelivery struct
    method PrepareDomain (line 168) | func (c *mtastsDelivery) PrepareDomain(ctx context.Context, domain str...
    method PrepareConn (line 175) | func (c *mtastsDelivery) PrepareConn(ctx context.Context, mx string) {}
    method CheckMX (line 177) | func (c *mtastsDelivery) CheckMX(ctx context.Context, mxLevel module.M...
    method CheckConn (line 199) | func (c *mtastsDelivery) CheckConn(ctx context.Context, mxLevel module...
    method Reset (line 234) | func (c *mtastsDelivery) Reset(msgMeta *module.MsgMetadata) {
  function NewMTASTSPolicy (line 57) | func NewMTASTSPolicy(c *container.C, modName, instName string) (module.M...
  type stsPreloadPolicy (line 242) | type stsPreloadPolicy struct
    method Name (line 254) | func (c *stsPreloadPolicy) Name() string {
    method InstanceName (line 258) | func (c *stsPreloadPolicy) InstanceName() string {
    method Weight (line 262) | func (c *stsPreloadPolicy) Weight() int {
    method Configure (line 266) | func (c *stsPreloadPolicy) Configure(inlineArgs []string, cfg *config....
    method StartDelivery (line 286) | func (p *stsPreloadPolicy) StartDelivery(*module.MsgMetadata) module.D...
  function NewSTSPreload (line 247) | func NewSTSPreload(c *container.C, modName, instName string) (module.Mod...
  type preloadDelivery (line 282) | type preloadDelivery struct
    method Reset (line 290) | func (p *preloadDelivery) Reset(*module.MsgMetadata)                  ...
    method PrepareDomain (line 291) | func (p *preloadDelivery) PrepareDomain(ctx context.Context, domain st...
    method PrepareConn (line 292) | func (p *preloadDelivery) PrepareConn(ctx context.Context, mx string) ...
    method CheckMX (line 293) | func (p *preloadDelivery) CheckMX(ctx context.Context, mxLevel module....
    method CheckConn (line 297) | func (p *preloadDelivery) CheckConn(ctx context.Context, mxLevel modul...
  type dnssecPolicy (line 301) | type dnssecPolicy struct
    method Name (line 311) | func (dnssecPolicy) Name() string {
    method InstanceName (line 315) | func (c dnssecPolicy) InstanceName() string {
    method Weight (line 319) | func (dnssecPolicy) Weight() int {
    method Configure (line 323) | func (dnssecPolicy) Configure(inlineArgs []string, cfg *config.Map) er...
    method StartDelivery (line 328) | func (dnssecPolicy) StartDelivery(*module.MsgMetadata) module.Delivery...
    method Reset (line 332) | func (dnssecPolicy) Reset(*module.MsgMetadata)                        {}
    method PrepareDomain (line 333) | func (dnssecPolicy) PrepareDomain(ctx context.Context, domain string) {}
    method PrepareConn (line 334) | func (dnssecPolicy) PrepareConn(ctx context.Context, mx string)       {}
    method CheckMX (line 336) | func (dnssecPolicy) CheckMX(ctx context.Context, mxLevel module.MXLeve...
    method CheckConn (line 343) | func (dnssecPolicy) CheckConn(ctx context.Context, mxLevel module.MXLe...
  function NewDNSSECPolicy (line 305) | func NewDNSSECPolicy(_ *container.C, _, instName string) (module.Module,...
  type danePolicy (line 348) | type danePolicy struct
    method Name (line 366) | func (c *danePolicy) Name() string {
    method InstanceName (line 370) | func (c *danePolicy) InstanceName() string {
    method Weight (line 374) | func (c *danePolicy) Weight() int {
    method Configure (line 378) | func (c *danePolicy) Configure(inlineArgs []string, cfg *config.Map) e...
    method StartDelivery (line 391) | func (c *danePolicy) StartDelivery(*module.MsgMetadata) module.Deliver...
  type daneDelivery (line 353) | type daneDelivery struct
    method PrepareDomain (line 395) | func (c *daneDelivery) PrepareDomain(ctx context.Context, domain strin...
    method discoverTLSA (line 397) | func (c *daneDelivery) discoverTLSA(ctx context.Context, mx string) ([...
    method PrepareConn (line 466) | func (c *daneDelivery) PrepareConn(ctx context.Context, mx string) {
    method CheckMX (line 486) | func (c *daneDelivery) CheckMX(ctx context.Context, mxLevel module.MXL...
    method CheckConn (line 490) | func (c *daneDelivery) CheckConn(ctx context.Context, mxLevel module.M...
    method Reset (line 524) | func (c *daneDelivery) Reset(*module.MsgMetadata) {}
  function NewDANEPolicy (line 359) | func NewDANEPolicy(c *container.C, modName, instName string) (module.Mod...
  type localPolicy (line 527) | type localPolicy struct
    method Name (line 540) | func (c *localPolicy) Name() string {
    method InstanceName (line 544) | func (c *localPolicy) InstanceName() string {
    method Weight (line 548) | func (c *localPolicy) Weight() int {
    method Configure (line 552) | func (c *localPolicy) Configure(inlineArgs []string, cfg *config.Map) ...
    method StartDelivery (line 587) | func (l *localPolicy) StartDelivery(msgMeta *module.MsgMetadata) modul...
    method Reset (line 591) | func (l *localPolicy) Reset(*module.MsgMetadata)                      ...
    method PrepareDomain (line 592) | func (l *localPolicy) PrepareDomain(ctx context.Context, domain string...
    method PrepareConn (line 593) | func (l *localPolicy) PrepareConn(ctx context.Context, mx string)     ...
    method CheckMX (line 595) | func (l *localPolicy) CheckMX(ctx context.Context, mxLevel module.MXLe...
    method CheckConn (line 612) | func (l *localPolicy) CheckConn(ctx context.Context, mxLevel module.MX...
  function NewLocalPolicy (line 534) | func NewLocalPolicy(_ *container.C, _, instName string) (module.Module, ...
  function init (line 627) | func init() {

FILE: internal/target/skeleton.go
  constant modName (line 37) | modName = "target.target_name"
  type Target (line 39) | type Target struct
    method Init (line 54) | func (t *Target) Init(cfg *config.Map) error {
    method Name (line 68) | func (t *Target) Name() string {
    method InstanceName (line 72) | func (t *Target) InstanceName() string {
    method Close (line 78) | func (t *Target) Close() error {
    method Start (line 93) | func (t *Target) Start(ctx context.Context, msgMeta *module.MsgMetadat...
  function New (line 44) | func New(_, instName string, _, inlineArgs []string) (module.Module, err...
  type delivery (line 82) | type delivery struct
    method AddRcpt (line 102) | func (d *delivery) AddRcpt(ctx context.Context, rcptTo string) error {
    method Body (line 107) | func (d *delivery) Body(ctx context.Context, header textproto.Header, ...
    method Abort (line 121) | func (d *delivery) Abort(ctx context.Context) error {
    method Commit (line 125) | func (d *delivery) Commit(ctx context.Context) error {
  function init (line 129) | func init() {

FILE: internal/target/smtp/sasl.go
  function saslAuthDirective (line 34) | func saslAuthDirective(_ *config.Map, node config.Node) (interface{}, er...

FILE: internal/target/smtp/sasl_test.go
  function testSaslFactory (line 31) | func testSaslFactory(t *testing.T, args ...string) saslClientFactory {
  function TestSASL_Plain (line 42) | func TestSASL_Plain(t *testing.T) {
  function TestSASL_Plain_AuthFail (line 72) | func TestSASL_Plain_AuthFail(t *testing.T) {
  function TestSASL_Login_Directive (line 104) | func TestSASL_Login_Directive(t *testing.T) {
  function TestSASL_Forward (line 121) | func TestSASL_Forward(t *testing.T) {
  function TestSASL_Forward_NoCreds (line 156) | func TestSASL_Forward_NoCreds(t *testing.T) {

FILE: internal/target/smtp/smtp_downstream.go
  type Downstream (line 52) | type Downstream struct
    method moduleError (line 70) | func (u *Downstream) moduleError(err error) error {
    method Configure (line 89) | func (u *Downstream) Configure(inlineArgs []string, cfg *config.Map) e...
    method Name (line 162) | func (u *Downstream) Name() string {
    method InstanceName (line 166) | func (u *Downstream) InstanceName() string {
    method StartDelivery (line 186) | func (u *Downstream) StartDelivery(ctx context.Context, msgMeta *modul...
  function New (line 80) | func New(c *container.C, modName, instName string) (module.Module, error) {
  type delivery (line 170) | type delivery struct
    method closeConn (line 213) | func (d *delivery) closeConn(c *smtpconn.C) {
    method connect (line 219) | func (d *delivery) connect(ctx context.Context) error {
    method AddRcpt (line 279) | func (d *delivery) AddRcpt(ctx context.Context, rcptTo string, opts sm...
    method Body (line 289) | func (d *delivery) Body(ctx context.Context, header textproto.Header, ...
    method Abort (line 340) | func (d *delivery) Abort(ctx context.Context) error {
    method Commit (line 344) | func (d *delivery) Commit(ctx context.Context) error {
  type lmtpDelivery (line 182) | type lmtpDelivery struct
    method BodyNonAtomic (line 303) | func (d *lmtpDelivery) BodyNonAtomic(ctx context.Context, sc module.St...
  function init (line 348) | func init() {

FILE: internal/target/smtp/smtp_downstream_test.go
  function TestDownstreamDelivery (line 38) | func TestDownstreamDelivery(t *testing.T) {
  function TestDownstreamDelivery_LMTP (line 71) | func TestDownstreamDelivery_LMTP(t *testing.T) {
  function TestDownstreamDelivery_LMTP_ErrorCoerce (line 124) | func TestDownstreamDelivery_LMTP_ErrorCoerce(t *testing.T) {
  type statusCollector (line 160) | type statusCollector
    method SetStatus (line 162) | func (sc *statusCollector) SetStatus(rcptTo string, err error) {
  function TestDownstreamDelivery_Fallback (line 166) | func TestDownstreamDelivery_Fallback(t *testing.T) {
  function TestDownstreamDelivery_MAILErr (line 194) | func TestDownstreamDelivery_MAILErr(t *testing.T) {
  function TestDownstreamDelivery_StartTLS (line 223) | func TestDownstreamDelivery_StartTLS(t *testing.T) {
  function TestDownstreamDelivery_StartTLS_NoFallback (line 253) | func TestDownstreamDelivery_StartTLS_NoFallback(t *testing.T) {
  function TestMain (line 279) | func TestMain(m *testing.M) {

FILE: internal/target/smtp/smtputf8_test.go
  function TestDownstreamDelivery_EHLO_ALabel (line 30) | func TestDownstreamDelivery_EHLO_ALabel(t *testing.T) {

FILE: internal/testutils/bench_delivery.go
  constant MessageBodySize (line 39) | MessageBodySize             = 100 * 1024
  constant ExtraMessageHeaderFields (line 40) | ExtraMessageHeaderFields    = 10
  constant ExtraMessageHeaderFieldSize (line 41) | ExtraMessageHeaderFieldSize = 50
  constant testHeaderString (line 44) | testHeaderString = "Content-Type: multipart/mixed; boundary=message-boun...
  constant testAltHeaderString (line 55) | testAltHeaderString = "Content-Type: multipart/alternative; boundary=b2\...
  constant testTextHeaderString (line 58) | testTextHeaderString = "Content-Disposition: inline\r\n" +
  constant testTextBodyString (line 62) | testTextBodyString = "What's your name?"
  constant testTextString (line 64) | testTextString = testTextHeaderString + testTextBodyString
  constant testHTMLHeaderString (line 66) | testHTMLHeaderString = "Content-Disposition: inline\r\n" +
  constant testHTMLBodyString (line 70) | testHTMLBodyString = "<div>What's <i>your</i> name?</div>"
  constant testHTMLString (line 72) | testHTMLString = testHTMLHeaderString + testHTMLBodyString
  constant testAttachmentHeaderString (line 74) | testAttachmentHeaderString = "Content-Disposition: attachment; filename=...
  constant testAttachmentBodyString (line 78) | testAttachmentBodyString = "My name is Mitsuha."
  constant testAttachmentString (line 80) | testAttachmentString = testAttachmentHeaderString + testAttachmentBodySt...
  constant testBodyString (line 82) | testBodyString = "--message-boundary\r\n" +
  function RandomMsg (line 95) | func RandomMsg(b *testing.B) (module.MsgMetadata, textproto.Header, buff...
  function BenchDelivery (line 112) | func BenchDelivery(b *testing.B, target module.DeliveryTarget, sender st...

FILE: internal/testutils/buffer.go
  function BodyFromStr (line 32) | func BodyFromStr(t *testing.T, literal string) (textproto.Header, buffer...
  type errorReader (line 48) | type errorReader struct
    method Read (line 53) | func (r *errorReader) Read(b []byte) (int, error) {
  type FailingBuffer (line 61) | type FailingBuffer struct
    method Open (line 68) | func (fb FailingBuffer) Open() (io.ReadCloser, error) {
    method Len (line 78) | func (fb FailingBuffer) Len() int {
    method Remove (line 82) | func (fb FailingBuffer) Remove() error {

FILE: internal/testutils/check.go
  type Check (line 32) | type Check struct
    method CheckStateForMsg (line 50) | func (c *Check) CheckStateForMsg(ctx context.Context, msgMeta *module....
    method Configure (line 59) | func (c *Check) Configure([]string, *config.Map) error {
    method Name (line 63) | func (c *Check) Name() string {
    method InstanceName (line 67) | func (c *Check) InstanceName() string {
    method CheckConnection (line 74) | func (c *Check) CheckConnection(ctx context.Context, state *module.Con...
  type checkState (line 78) | type checkState struct
    method CheckConnection (line 83) | func (cs *checkState) CheckConnection(ctx context.Context) module.Chec...
    method CheckSender (line 88) | func (cs *checkState) CheckSender(ctx context.Context, from string) mo...
    method CheckRcpt (line 93) | func (cs *checkState) CheckRcpt(ctx context.Context, to string) module...
    method CheckBody (line 98) | func (cs *checkState) CheckBody(ctx context.Context, header textproto....
    method Close (line 103) | func (cs *checkState) Close() error {
  function init (line 108) | func init() {

FILE: internal/testutils/filesystem.go
  function Dir (line 28) | func Dir(t *testing.T) string {

FILE: internal/testutils/logger.go
  function Logger (line 36) | func Logger(t *testing.T, name string) *log.Logger {

FILE: internal/testutils/modifier.go
  type Modifier (line 32) | type Modifier struct
    method Configure (line 47) | func (m Modifier) Configure([]string, *config.Map) error {
    method Name (line 51) | func (m Modifier) Name() string {
    method InstanceName (line 55) | func (m Modifier) InstanceName() string {
    method ModStateForMsg (line 63) | func (m Modifier) ModStateForMsg(ctx context.Context, msgMeta *module....
  type modifierState (line 59) | type modifierState struct
    method RewriteSender (line 72) | func (ms modifierState) RewriteSender(ctx context.Context, mailFrom st...
    method RewriteRcpt (line 87) | func (ms modifierState) RewriteRcpt(ctx context.Context, rcptTo string...
    method RewriteBody (line 103) | func (ms modifierState) RewriteBody(ctx context.Context, h *textproto....
    method Close (line 114) | func (ms modifierState) Close() error {
  function init (line 119) | func init() {

FILE: internal/testutils/multitable.go
  type MultiTable (line 23) | type MultiTable struct
    method LookupMulti (line 28) | func (m MultiTable) LookupMulti(_ context.Context, a string) ([]string...

FILE: internal/testutils/smtp_server.go
  type SMTPMessage (line 39) | type SMTPMessage struct
  type SMTPBackend (line 49) | type SMTPBackend struct
    method NewSession (line 64) | func (be *SMTPBackend) NewSession(conn *smtp.Conn) (smtp.Session, erro...
    method ConnectionCount (line 77) | func (be *SMTPBackend) ConnectionCount() int {
    method CheckMsg (line 81) | func (be *SMTPBackend) CheckMsg(t *testing.T, indx int, from string, r...
  type session (line 105) | type session struct
    method AuthMechanisms (line 113) | func (s *session) AuthMechanisms() []string {
    method Auth (line 117) | func (s *session) Auth(mech string) (sasl.Server, error) {
    method Reset (line 131) | func (s *session) Reset() {
    method Logout (line 135) | func (s *session) Logout() error {
    method Mail (line 140) | func (s *session) Mail(from string, opts *smtp.MailOptions) error {
    method Rcpt (line 153) | func (s *session) Rcpt(to string, _ *smtp.RcptOptions) error {
    method Data (line 162) | func (s *session) Data(r io.Reader) error {
    method LMTPData (line 179) | func (s *session) LMTPData(r io.Reader, status smtp.StatusCollector) e...
  type SMTPServerConfigureFunc (line 201) | type SMTPServerConfigureFunc
  function SMTPServer (line 203) | func SMTPServer(t *testing.T, addr string, fn ...SMTPServerConfigureFunc...
  constant testServerCert (line 239) | testServerCert = `-----BEGIN CERTIFICATE-----
  constant testServerKey (line 254) | testServerKey = `-----BEGIN PRIVATE KEY-----
  function SMTPServerSTARTTLS (line 276) | func SMTPServerSTARTTLS(t *testing.T, addr string, fn ...SMTPServerConfi...
  function SMTPServerTLS (line 331) | func SMTPServerTLS(t *testing.T, addr string, fn ...SMTPServerConfigureF...
  type smtpBackendConnCounter (line 384) | type smtpBackendConnCounter interface
  function CheckSMTPConnLeak (line 388) | func CheckSMTPConnLeak(t *testing.T, srv *smtp.Server) {
  function WaitForConnsClose (line 408) | func WaitForConnsClose(t *testing.T, srv *smtp.Server) {
  function FailOnConn (line 415) | func FailOnConn(t *testing.T, addr string) net.Listener {
  function CheckSMTPErr (line 433) | func CheckSMTPErr(t *testing.T, err error, code int, enchCode exterrors....

FILE: internal/testutils/table.go
  type Table (line 23) | type Table struct
    method Lookup (line 28) | func (m Table) Lookup(_ context.Context, a string) (string, bool, erro...

FILE: internal/testutils/target.go
  type Msg (line 39) | type Msg struct
  type Target (line 47) | type Target struct
    method Configure (line 65) | func (dt *Target) Configure([]string, *config.Map) error {
    method InstanceName (line 69) | func (dt *Target) InstanceName() string {
    method Name (line 76) | func (dt *Target) Name() string {
    method StartDelivery (line 89) | func (dt *Target) StartDelivery(ctx context.Context, msgMeta *module.M...
  type testTargetDelivery (line 80) | type testTargetDelivery struct
    method AddRcpt (line 104) | func (dtd *testTargetDelivery) AddRcpt(ctx context.Context, to string,...
    method Body (line 146) | func (dtd *testTargetDelivery) Body(ctx context.Context, header textpr...
    method Abort (line 176) | func (dtd *testTargetDelivery) Abort(ctx context.Context) error {
    method Commit (line 180) | func (dtd *testTargetDelivery) Commit(ctx context.Context) error {
  type testTargetDeliveryPartial (line 85) | type testTargetDeliveryPartial struct
    method BodyNonAtomic (line 115) | func (dtd *testTargetDeliveryPartial) BodyNonAtomic(ctx context.Contex...
  function DoTestDelivery (line 191) | func DoTestDelivery(t *testing.T, tgt module.DeliveryTarget, from string...
  function DoTestDeliveryMeta (line 198) | func DoTestDeliveryMeta(t *testing.T, tgt module.DeliveryTarget, from st...
  function DoTestDeliveryNonAtomic (line 208) | func DoTestDeliveryNonAtomic(t *testing.T, c module.StatusCollector, tgt...
  constant DeliveryData (line 254) | DeliveryData = "A: 1\r\n" +
  function DoTestDeliveryErr (line 259) | func DoTestDeliveryErr(t *testing.T, tgt module.DeliveryTarget, from str...
  function DoTestDeliveryErrMeta (line 263) | func DoTestDeliveryErrMeta(t *testing.T, tgt module.DeliveryTarget, from...
  function CheckTestMessage (line 311) | func CheckTestMessage(t *testing.T, tgt *Target, indx int, sender string...
  function CheckMsg (line 323) | func CheckMsg(t *testing.T, msg *Msg, sender string, rcpt []string) {
  function CheckMsgID (line 332) | func CheckMsgID(t *testing.T, msg *Msg, sender string, rcpt []string, id...

FILE: internal/tls/acme/acme.go
  constant modName (line 19) | modName = "tls.loader.acme"
  type Loader (line 21) | type Loader struct
    method Configure (line 40) | func (l *Loader) Configure(inlineArgs []string, cfg *config.Map) error {
    method ConfigureTLS (line 130) | func (l *Loader) ConfigureTLS(c *tls.Config) error {
    method Start (line 135) | func (l *Loader) Start() error {
    method Stop (line 146) | func (l *Loader) Stop() error {
    method Name (line 152) | func (l *Loader) Name() string {
    method InstanceName (line 156) | func (l *Loader) InstanceName() string {
  function New (line 33) | func New(c *container.C, _, instName string) (module.Module, error) {
  function init (line 160) | func init() {
  function init (line 166) | func init() {

FILE: internal/tls/file.go
  type FileLoader (line 36) | type FileLoader struct
    method Configure (line 57) | func (f *FileLoader) Configure(inlineArgs []string, cfg *config.Map) e...
    method Start (line 89) | func (f *FileLoader) Start() error {
    method Reload (line 95) | func (f *FileLoader) Reload() error {
    method Stop (line 100) | func (f *FileLoader) Stop() error {
    method Name (line 106) | func (f *FileLoader) Name() string {
    method InstanceName (line 110) | func (f *FileLoader) InstanceName() string {
    method reloadTicker (line 114) | func (f *FileLoader) reloadTicker() {
    method loadCerts (line 128) | func (f *FileLoader) loadCerts() error {
    method ConfigureTLS (line 157) | func (f *FileLoader) ConfigureTLS(c *tls.Config) error {
  function NewFileLoader (line 49) | func NewFileLoader(c *container.C, modName, instName string) (module.Mod...
  function init (line 166) | func init() {

FILE: internal/tls/self_signed.go
  type SelfSignedLoader (line 38) | type SelfSignedLoader struct
    method Configure (line 51) | func (f *SelfSignedLoader) Configure(inlineArgs []string, cfg *config....
    method Name (line 98) | func (f *SelfSignedLoader) Name() string {
    method InstanceName (line 102) | func (f *SelfSignedLoader) InstanceName() string {
    method ConfigureTLS (line 106) | func (f *SelfSignedLoader) ConfigureTLS(c *tls.Config) error {
  function NewSelfSignedLoader (line 45) | func NewSelfSignedLoader(_ *container.C, _, instName string) (module.Mod...
  function init (line 111) | func init() {

FILE: internal/updatepipe/backend.go
  type BackendMode (line 21) | type BackendMode
  constant ModeReplicate (line 26) | ModeReplicate BackendMode = iota
  constant ModePush (line 32) | ModePush BackendMode = iota
  type Backend (line 38) | type Backend interface

FILE: internal/updatepipe/pubsub/pq.go
  type Msg (line 12) | type Msg struct
  type PqPubSub (line 17) | type PqPubSub struct
    method Close (line 52) | func (l *PqPubSub) Close() error {
    method eventHandler (line 62) | func (l *PqPubSub) eventHandler(ev pq.ListenerEventType, err error) {
    method Subscribe (line 75) | func (l *PqPubSub) Subscribe(_ context.Context, key string) error {
    method Unsubscribe (line 79) | func (l *PqPubSub) Unsubscribe(_ context.Context, key string) error {
    method Publish (line 83) | func (l *PqPubSub) Publish(key, payload string) error {
    method Listener (line 88) | func (l *PqPubSub) Listener() chan Msg {
  function NewPQ (line 26) | func NewPQ(dsn string) (*PqPubSub, error) {

FILE: internal/updatepipe/pubsub/pubsub.go
  type PubSub (line 5) | type PubSub interface

FILE: internal/updatepipe/pubsub_pipe.go
  type PubSubPipe (line 14) | type PubSubPipe struct
    method Listen (line 19) | func (p *PubSubPipe) Listen(upds chan<- mess.Update) error {
    method InitPush (line 36) | func (p *PubSubPipe) InitPush() error {
    method myID (line 40) | func (p *PubSubPipe) myID() string {
    method channel (line 44) | func (p *PubSubPipe) channel(key interface{}) (string, error) {
    method Subscribe (line 57) | func (p *PubSubPipe) Subscribe(key interface{}) {
    method Unsubscribe (line 71) | func (p *PubSubPipe) Unsubscribe(key interface{}) {
    method Push (line 85) | func (p *PubSubPipe) Push(upd mess.Update) error {
    method Close (line 99) | func (p *PubSubPipe) Close() error {

FILE: internal/updatepipe/serialize.go
  function unescapeName (line 31) | func unescapeName(s string) string {
  function escapeName (line 35) | func escapeName(s string) string {
  function parseUpdate (line 39) | func parseUpdate(s string) (id string, upd *mess.Update, err error) {
  function formatUpdate (line 60) | func formatUpdate(myID string, upd mess.Update) (string, error) {

FILE: internal/updatepipe/unix_pipe.go
  type UnixSockPipe (line 46) | type UnixSockPipe struct
    method myID (line 56) | func (usp *UnixSockPipe) myID() string {
    method readUpdates (line 60) | func (usp *UnixSockPipe) readUpdates(conn net.Conn, updCh chan<- mess....
    method Listen (line 77) | func (usp *UnixSockPipe) Listen(upd chan<- mess.Update) error {
    method InitPush (line 95) | func (usp *UnixSockPipe) InitPush() error {
    method Push (line 105) | func (usp *UnixSockPipe) Push(upd mess.Update) error {
    method Close (line 121) | func (usp *UnixSockPipe) Close() error {

FILE: internal/updatepipe/update_pipe.go
  type P (line 37) | type P interface

FILE: maddy.go
  function BuildInfo (line 89) | func BuildInfo() string {
  function init (line 106) | func init() {
  function Run (line 183) | func Run(c *cli.Context) error {
  function VerifyConfig (line 232) | func VerifyConfig(c *cli.Context) error {
  function initDebug (line 247) | func initDebug(c *cli.Context) {
  function InitDirs (line 270) | func InitDirs(c *container.C) error {
  function ensureDirectoryWritable (line 313) | func ensureDirectoryWritable(path string) error {
  function ReadGlobals (line 328) | func ReadGlobals(c *container.C, cfg []config.Node) (map[string]interfac...
  function ReadConfig (line 348) | func ReadConfig(path string) ([]config.Node, error) {
  function moduleConfigure (line 362) | func moduleConfigure(configPath string) (*container.C, error) {
  function moduleStart (line 398) | func moduleStart(c *container.C) error {
  function moduleStop (line 402) | func moduleStop(c *container.C, earlyStop bool) error {
  function moduleMain (line 412) | func moduleMain(configPath string) error {
  function moduleReload (line 461) | func moduleReload(oldContainer *container.C, configPath string, asyncSto...
  function RegisterModules (line 530) | func RegisterModules(c *container.C, globals map[string]interface{}, nod...

FILE: maddy_debug.go
  function init (line 28) | func init() {

FILE: signal.go
  function handleSignals (line 39) | func handleSignals() (reload bool) {

FILE: signal_nonposix.go
  function handleSignals (line 32) | func handleSignals() os.Signal {

FILE: systemd.go
  type SDStatus (line 36) | type SDStatus
  constant SDReady (line 39) | SDReady     = "READY=1"
  constant SDReloading (line 40) | SDReloading = "RELOADING=1"
  constant SDStopping (line 41) | SDStopping  = "STOPPING=1"
  function sdNotifySock (line 46) | func sdNotifySock() (*net.UnixConn, error) {
  function setScmPassCred (line 61) | func setScmPassCred(sock *net.UnixConn) error {
  function systemdStatus (line 79) | func systemdStatus(status SDStatus, desc string) {
  function systemdStatusErr (line 110) | func systemdStatusErr(reportedErr error) {

FILE: systemd_nonlinux.go
  type SDStatus (line 24) | type SDStatus
  constant SDReady (line 27) | SDReady     = "READY=1"
  constant SDReloading (line 28) | SDReloading = "RELOADING=1"
  constant SDStopping (line 29) | SDStopping  = "STOPPING=1"
  function systemdStatus (line 32) | func systemdStatus(SDStatus, string) {}
  function systemdStatusErr (line 34) | func systemdStatusErr(error) {}

FILE: tests/basic_test.go
  function TestBasic (line 30) | func TestBasic(tt *testing.T) {

FILE: tests/conn.go
  type Conn (line 35) | type Conn struct
    method AllowIOErr (line 51) | func (c *Conn) AllowIOErr(ok bool) {
    method Write (line 56) | func (c *Conn) Write(s string) {
    method Writeln (line 76) | func (c *Conn) Writeln(s string) {
    method Readln (line 82) | func (c *Conn) Readln() (string, error) {
    method Expect (line 114) | func (c *Conn) Expect(line string) {
    method ExpectPattern (line 130) | func (c *Conn) ExpectPattern(pat string) string {
    method fatal (line 149) | func (c *Conn) fatal(f string, args ...interface{}) {
    method log (line 155) | func (c *Conn) log(direction rune, f string, args ...interface{}) {
    method TLS (line 189) | func (c *Conn) TLS() {
    method SMTPPlainAuth (line 204) | func (c *Conn) SMTPPlainAuth(username, password string, expectOk bool) {
    method SMTPNegotation (line 218) | func (c *Conn) SMTPNegotation(ourName string, requireExts, blacklistEx...
    method Close (line 281) | func (c *Conn) Close() error {
    method MustClose (line 285) | func (c *Conn) MustClose() {
    method Rebind (line 292) | func (c *Conn) Rebind(subtest *T) *Conn {

FILE: tests/cover_test.go
  function TestMain (line 51) | func TestMain(m *testing.M) {

FILE: tests/dovecot_sasl_test.go
  function init (line 44) | func init() {
  constant dovecotConf (line 48) | dovecotConf = `
  constant dovecotConf24 (line 99) | dovecotConf24 = `dovecot_config_version = 2.4.0
  constant dovecotPasswd (line 139) | dovecotPasswd = `tester:{plain}123456:1000:1000::/home/user`
  function isDovecot24 (line 141) | func isDovecot24(t *testing.T, dovecotExec string) bool {
  function runDovecot (line 157) | func runDovecot(t *testing.T) (string, *exec.Cmd) {
  function cleanDovecot (line 235) | func cleanDovecot(t *testing.T, tempDir string, cmd *exec.Cmd) {
  function TestDovecotSASLClient (line 244) | func TestDovecotSASLClient(tt *testing.T) {

FILE: tests/dovecot_sasld_test.go
  function init (line 42) | func init() {
  constant chasquidConf (line 46) | chasquidConf = `smtp_address: "127.0.0.2:44444"
  constant testServerCert (line 59) | testServerCert = `-----BEGIN CERTIFICATE-----
  constant testServerKey (line 74) | testServerKey = `-----BEGIN PRIVATE KEY-----
  function runChasquid (line 91) | func runChasquid(t *testing.T, authClientPath string) (string, *exec.Cmd) {
  function cleanChasquid (line 158) | func cleanChasquid(t *testing.T, tempDir string, cmd *exec.Cmd) {
  function TestSASLServerWithChasquid (line 163) | func TestSASLServerWithChasquid(tt *testing.T) {

FILE: tests/ghsa_5835_4gvc_32pc_test.go
  type searchEntry (line 15) | type searchEntry struct
  type MockLDAP (line 20) | type MockLDAP struct
    method HandleBind (line 26) | func (ml *MockLDAP) HandleBind(w *gldap.ResponseWriter, r *gldap.Reque...
    method HandleSearch (line 46) | func (ml *MockLDAP) HandleSearch(w *gldap.ResponseWriter, r *gldap.Req...
    method Run (line 68) | func (ml *MockLDAP) Run(address string) {
  function TestLDAPInjectionFilter (line 95) | func TestLDAPInjectionFilter(tt *testing.T) {

FILE: tests/gocovcat.go
  function handleErr (line 36) | func handleErr(err error) {
  function main (line 43) | func main() {

FILE: tests/imap_test.go
  function TestIMAPEndpointAuthMap (line 12) | func TestIMAPEndpointAuthMap(tt *testing.T) {
  function TestIMAPEndpointStorageMap (line 52) | func TestIMAPEndpointStorageMap(tt *testing.T) {

FILE: tests/imapsql_test.go
  function TestImapsqlDelivery (line 32) | func TestImapsqlDelivery(tt *testing.T) {
  function TestImapsqlDeliveryMap (line 116) | func TestImapsqlDeliveryMap(tt *testing.T) {
  function TestImapsqlAuthMap (line 188) | func TestImapsqlAuthMap(tt *testing.T) {

FILE: tests/issue327_test.go
  function TestIssue327 (line 33) | func TestIssue327(tt *testing.T) {

FILE: tests/limits_test.go
  function TestConcurrencyLimit (line 30) | func TestConcurrencyLimit(tt *testing.T) {
  function TestPerIPConcurrency (line 66) | func TestPerIPConcurrency(tt *testing.T) {

FILE: tests/lmtp_test.go
  function TestLMTPServer_Is_Actually_LMTP (line 33) | func TestLMTPServer_Is_Actually_LMTP(tt *testing.T) {
  function TestLMTPClient_Is_Actually_LMTP (line 88) | func TestLMTPClient_Is_Actually_LMTP(tt *testing.T) {
  function TestLMTPClient_Issue308 (line 133) | func TestLMTPClient_Issue308(tt *testing.T) {

FILE: tests/modules_test.go
  function TestConfigCycle (line 29) | func TestConfigCycle(tt *testing.T) {

FILE: tests/mta_test.go
  function TestMTA_Outbound (line 34) | func TestMTA_Outbound(tt *testing.T) {
  function TestIssue321 (line 84) | func TestIssue321(tt *testing.T) {

FILE: tests/multiple_domains_test.go
  function TestMultipleDomains_SeparateNamespace (line 31) | func TestMultipleDomains_SeparateNamespace(tt *testing.T) {
  function TestMultipleDomains_SharedCredentials_DistinctMailboxes (line 128) | func TestMultipleDomains_SharedCredentials_DistinctMailboxes(tt *testing...
  function TestMultipleDomains_SharedCredentials_SharedMailboxes (line 225) | func TestMultipleDomains_SharedCredentials_SharedMailboxes(tt *testing.T) {

FILE: tests/reload_non_unix.go
  method reloadConfig (line 23) | func (t *T) reloadConfig() {

FILE: tests/reload_test.go
  function TestSmtpPipelineSwitch (line 33) | func TestSmtpPipelineSwitch(tt *testing.T) {
  function TestImapStorageSwitch (line 98) | func TestImapStorageSwitch(tt *testing.T) {

FILE: tests/reload_unix.go
  method reloadConfig (line 28) | func (t *T) reloadConfig() {

FILE: tests/replace_addr_test.go
  function TestReplaceAddr_Rcpt (line 30) | func TestReplaceAddr_Rcpt(tt *testing.T) {
  function TestReplaceAddr_Sender (line 134) | func TestReplaceAddr_Sender(tt *testing.T) {

FILE: tests/smtp_autobuffer_test.go
  function TestSMTPEndpoint_LargeMessage (line 32) | func TestSMTPEndpoint_LargeMessage(tt *testing.T) {
  function TestSMTPEndpoint_FileBuffer (line 123) | func TestSMTPEndpoint_FileBuffer(tt *testing.T) {
  function TestSMTPEndpoint_Autobuffer (line 213) | func TestSMTPEndpoint_Autobuffer(tt *testing.T) {

FILE: tests/smtp_test.go
  function TestCheckRequireTLS (line 36) | func TestCheckRequireTLS(tt *testing.T) {
  function TestProxyProtocolTrustedSource (line 72) | func TestProxyProtocolTrustedSource(tt *testing.T) {
  function TestProxyProtocolUntrustedSource (line 116) | func TestProxyProtocolUntrustedSource(tt *testing.T) {
  function TestCheckSPF (line 160) | func TestCheckSPF(tt *testing.T) {
  function TestSPF_DMARCDefer (line 253) | func TestSPF_DMARCDefer(tt *testing.T) {
  function TestDNSBLConfig (line 348) | func TestDNSBLConfig(tt *testing.T) {
  function TestDNSBLConfig2 (line 397) | func TestDNSBLConfig2(tt *testing.T) {
  function TestCheckAuthorizeSender (line 446) | func TestCheckAuthorizeSender(tt *testing.T) {
  function TestCheckCommand (line 517) | func TestCheckCommand(tt *testing.T) {
  function TestHeaderSizeConstraint (line 631) | func TestHeaderSizeConstraint(tt *testing.T) {

FILE: tests/stress_test.go
  function floodSmtp (line 32) | func floodSmtp(c *tests.Conn, commands, expectedPatterns []string, itera...
  function TestSMTPFlood_FullMsg_NoLimits_1Conn (line 43) | func TestSMTPFlood_FullMsg_NoLimits_1Conn(tt *testing.T) {
  function TestSMTPFlood_FullMsg_NoLimits_10Conns (line 81) | func TestSMTPFlood_FullMsg_NoLimits_10Conns(tt *testing.T) {
  function TestSMTPFlood_EnvelopeAbort_NoLimits_10Conns (line 129) | func TestSMTPFlood_EnvelopeAbort_NoLimits_10Conns(tt *testing.T) {
  function TestSMTPFlood_EnvelopeAbort_Ratelimited (line 169) | func TestSMTPFlood_EnvelopeAbort_Ratelimited(tt *testing.T) {
  function TestSMTPFlood_FullMsg_Ratelimited_PerSource (line 230) | func TestSMTPFlood_FullMsg_Ratelimited_PerSource(tt *testing.T) {
  function TestSMTPFlood_EnvelopeAbort_Ratelimited_PerIP (line 329) | func TestSMTPFlood_EnvelopeAbort_Ratelimited_PerIP(tt *testing.T) {

FILE: tests/t.go
  type T (line 53) | type T struct
    method Config (line 80) | func (t *T) Config(cfg string) {
    method DNS (line 104) | func (t *T) DNS(zones map[string]mockdns.Zone) {
    method Port (line 146) | func (t *T) Port(name string) uint16 {
    method Env (line 158) | func (t *T) Env(kv string) {
    method ensureCanRun (line 162) | func (t *T) ensureCanRun() {
    method buildCmd (line 210) | func (t *T) buildCmd(additionalArgs ...string) *exec.Cmd {
    method MustRunCLIGroup (line 253) | func (t *T) MustRunCLIGroup(args ...[]string) {
    method MustRunCLI (line 272) | func (t *T) MustRunCLI(args ...string) string {
    method RunCLI (line 280) | func (t *T) RunCLI(args ...string) (string, error) {
    method Run (line 306) | func (t *T) Run(waitListeners int) {
    method StateDir (line 359) | func (t *T) StateDir() string {
    method RuntimeDir (line 363) | func (t *T) RuntimeDir() string {
    method killServer (line 367) | func (t *T) killServer() {
    method Close (line 394) | func (t *T) Close() {
    method Printf (line 399) | func (t *T) Printf(f string, a ...interface{}) {
    method Conn6 (line 404) | func (t *T) Conn6(portName string) Conn {
    method Conn4 (line 426) | func (t *T) Conn4(sourceIP, portName string) Conn {
    method ConnUnnamed (line 465) | func (t *T) ConnUnnamed(port uint16) Conn {
    method Conn (line 486) | func (t *T) Conn(portName string) Conn {
    method Subtest (line 495) | func (t *T) Subtest(name string, f func(t *T)) {
  function NewT (line 69) | func NewT(t *testing.T) *T {
  function init (line 503) | func init() {
Condensed preview — 448 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,085K chars).
[
  {
    "path": ".dockerignore",
    "chars": 50,
    "preview": "testdata/\ncmd/maddy/maddy\nmaddy\ntests/maddy.cover\n"
  },
  {
    "path": ".editorconfig",
    "chars": 187,
    "preview": "root = true\n\n[*]\nend_of_line = lf\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n\n[*.{scd,go}]\nindent_style"
  },
  {
    "path": ".gitattributes",
    "chars": 19,
    "preview": "* text=auto eol=lf\n"
  },
  {
    "path": ".github/CODE_OF_CONDUCT.md",
    "chars": 2309,
    "preview": "# Code of Merit\n\n**1.** The project creators, lead developers, core team, constitute the managing\nmembers of the project"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 2272,
    "preview": "# Contributing Guidelines\n\nOf course, we love our contributors. Thanks for spending time on making maddy\nbetter.\n\n## Rep"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "chars": 442,
    "preview": "---\nname: Bug report\nabout: If you think something is broken\ntitle: Bug report\nlabels: bug\nassignees: ''\n\n---\n\n# Describ"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 327,
    "preview": "contact_links:\n  - name: Questions \n    url: \"https://github.com/foxcpp/maddy/discussions/new?category=q-a\"\n    about: \""
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "chars": 476,
    "preview": "---\nname: Feature request\nabout: If you would like to see a new feature in maddy.\ntitle: Feature request\nlabels: new fea"
  },
  {
    "path": ".github/SECURITY.md",
    "chars": 528,
    "preview": "# Security Policy\n\n## Supported Versions\n\nTwo latest incompatible releases (e.g. 2.0.0 and 1.9.0).\n\nLatest release gets "
  },
  {
    "path": ".github/releases.md",
    "chars": 1139,
    "preview": "# Release preparation\n\n1. Run linters, fix all simple warnings. If the behavior is intentional - add\n`nolint` comment an"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 5774,
    "preview": "name: \"Prepare release artifacts\"\n\non:\n  push:\n    tags: [ \"v*\" ]\n\npermissions:\n  id-token: write\n  contents: read\n  att"
  },
  {
    "path": ".github/workflows/test.yml",
    "chars": 1535,
    "preview": "name: \"Testing\"\n\non:\n  push:\n    branches: [ master, dev ]\n    tags: [ \"v*\" ]\n  pull_request:\n    branches: [ master, de"
  },
  {
    "path": ".gitignore",
    "chars": 545,
    "preview": "# gitignore.io\n*.o\n*.a\n*.so\n_obj\n_test\n*.[568vq]\n[568vq].out\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export"
  },
  {
    "path": ".golangci.yml",
    "chars": 361,
    "preview": "version: \"2\"\nlinters:\n  enable:\n  - errcheck\n  - staticcheck\n  - ineffassign\n  - govet\n  - unused\n  - prealloc\n  - uncon"
  },
  {
    "path": ".mkdocs.yml",
    "chars": 2615,
    "preview": "site_name: maddy\n\nrepo_url: https://github.com/foxcpp/maddy\n\ntheme: alb\n\nmarkdown_extensions:\n  - codehilite:\n      gues"
  },
  {
    "path": ".version",
    "chars": 6,
    "preview": "0.9.4\n"
  },
  {
    "path": "AGENTS.md",
    "chars": 3575,
    "preview": "# AGENTS.md — Maddy Mail Server\n\n## Architecture\n\nMaddy is a composable all-in-one mail server (MTA/MX/IMAP) written in "
  },
  {
    "path": "COPYING",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "Dockerfile",
    "chars": 870,
    "preview": "FROM golang:1.23-alpine AS build-env\n\nARG ADDITIONAL_BUILD_TAGS=\"\"\n\nRUN set -ex && \\\n    apk upgrade --no-cache --availa"
  },
  {
    "path": "HACKING.md",
    "chars": 6154,
    "preview": "## Design goals\n\n- **Make it easy to deploy.**\n  Minimal configuration changes should be required to get a typical mail "
  },
  {
    "path": "README.md",
    "chars": 1300,
    "preview": "Maddy Mail Server\n=====================\n> Composable all-in-one mail server.\n\nMaddy Mail Server implements all functiona"
  },
  {
    "path": "build.sh",
    "chars": 5224,
    "preview": "#!/bin/sh\n\ndestdir=/\nbuilddir=\"$PWD/build\"\nprefix=/usr/local\nversion=\nstatic=0\nif [ \"${GOFLAGS}\" = \"\" ]; then\n\tGOFLAGS=\""
  },
  {
    "path": "cmd/README.md",
    "chars": 223,
    "preview": "maddy executables\n-------------------\n\n### maddy\n\nMain server executable.\n\n### maddy-pam-helper, maddy-shadow-helper\n\nUt"
  },
  {
    "path": "cmd/maddy/main.go",
    "chars": 947,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "cmd/maddy-pam-helper/README.md",
    "chars": 1786,
    "preview": "## maddy-pam-helper\n\nExternal setuid binary for interaction with shadow passwords database or other\nprivileged objects n"
  },
  {
    "path": "cmd/maddy-pam-helper/maddy.conf",
    "chars": 65,
    "preview": "#%PAM-1.0\nauth\trequired\tpam_unix.so\naccount\trequired\tpam_unix.so\n"
  },
  {
    "path": "cmd/maddy-pam-helper/main.c",
    "chars": 1158,
    "preview": "#define _POSIX_C_SOURCE 200809L\n#include <stdio.h>\n#include <stdlib.h>\n#include <security/pam_appl.h>\n#include \"pam.h\"\n\n"
  },
  {
    "path": "cmd/maddy-pam-helper/main.go",
    "chars": 1210,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "cmd/maddy-pam-helper/pam.c",
    "chars": 3308,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2022 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "cmd/maddy-pam-helper/pam.h",
    "chars": 941,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "cmd/maddy-shadow-helper/README.md",
    "chars": 1410,
    "preview": "## maddy-shadow-helper\n\nExternal helper binary for interaction with shadow passwords database.\nUnlike maddy-pam-helper i"
  },
  {
    "path": "cmd/maddy-shadow-helper/main.go",
    "chars": 1641,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "config.go",
    "chars": 3359,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "contrib/README.md",
    "chars": 242,
    "preview": "# Community contributed resources\n\nDisclaimer: Nothing inside subdirectories here is directly supported by Maddy\nMail Se"
  },
  {
    "path": "contrib/kubernetes/chart/.helmignore",
    "chars": 349,
    "preview": "# Patterns to ignore when building packages.\n# This supports shell glob matching, relative path matching, and\n# negation"
  },
  {
    "path": "contrib/kubernetes/chart/Chart.yaml",
    "chars": 1095,
    "preview": "apiVersion: v2\nname: maddy\ndescription: A Helm chart for Kubernetes\n\n# A chart can be either an 'application' or a 'libr"
  },
  {
    "path": "contrib/kubernetes/chart/README.md",
    "chars": 3945,
    "preview": "# maddy Helm chart for Kubernetes\n\n![Version: 0.2.5](https://img.shields.io/badge/Version-0.2.5-informational?style=flat"
  },
  {
    "path": "contrib/kubernetes/chart/files/aliases",
    "chars": 37,
    "preview": "info@example.org: foxcpp@example.org\n"
  },
  {
    "path": "contrib/kubernetes/chart/files/maddy.conf",
    "chars": 4535,
    "preview": "## maddy 0.3 - default configuration file (2020-05-31)\n# Suitable for small-scale deployments. Uses its own format for l"
  },
  {
    "path": "contrib/kubernetes/chart/templates/NOTES.txt",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "contrib/kubernetes/chart/templates/_helpers.tpl",
    "chars": 1762,
    "preview": "{{/*\nExpand the name of the chart.\n*/}}\n{{- define \"maddy.name\" -}}\n{{- default .Chart.Name .Values.nameOverride | trunc"
  },
  {
    "path": "contrib/kubernetes/chart/templates/configmap.yaml",
    "chars": 303,
    "preview": "apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: {{include \"maddy.fullname\" .}}\n  labels: {{- include \"maddy.labels\" . |"
  },
  {
    "path": "contrib/kubernetes/chart/templates/deployment.yaml",
    "chars": 3761,
    "preview": "apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: {{ include \"maddy.fullname\" . }}\n  labels:\n    {{- include \"maddy"
  },
  {
    "path": "contrib/kubernetes/chart/templates/pvc.yaml",
    "chars": 605,
    "preview": "{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) -}}\napiVersion: v1\nkind: PersistentVolume"
  },
  {
    "path": "contrib/kubernetes/chart/templates/service.yaml",
    "chars": 594,
    "preview": "apiVersion: v1\nkind: Service\nmetadata:\n  name: {{ include \"maddy.fullname\" . }}\n  labels:\n    {{- include \"maddy.labels\""
  },
  {
    "path": "contrib/kubernetes/chart/templates/serviceaccount.yaml",
    "chars": 316,
    "preview": "{{- if .Values.serviceAccount.create -}}\napiVersion: v1\nkind: ServiceAccount\nmetadata:\n  name: {{ include \"maddy.service"
  },
  {
    "path": "contrib/kubernetes/chart/templates/tests/test-connection.yaml",
    "chars": 381,
    "preview": "apiVersion: v1\nkind: Pod\nmetadata:\n  name: \"{{ include \"maddy.fullname\" . }}-test-connection\"\n  labels:\n    {{- include "
  },
  {
    "path": "contrib/kubernetes/chart/values.yaml",
    "chars": 1852,
    "preview": "# Default values for maddy.\n# This is a YAML-formatted file.\n# Declare variables to be passed into your templates.\n\nrepl"
  },
  {
    "path": "directories.go",
    "chars": 1478,
    "preview": "//go:build !docker\n// +build !docker\n\npackage maddy\n\nvar (\n\t// ConfigDirectory specifies platform-specific value\n\t// tha"
  },
  {
    "path": "directories_docker.go",
    "chars": 207,
    "preview": "//go:build docker\n// +build docker\n\npackage maddy\n\nvar (\n\tConfigDirectory         = \"/data\"\n\tDefaultStateDirectory   = \""
  },
  {
    "path": "dist/README.md",
    "chars": 1502,
    "preview": "Distribution files for maddy\n------------------------------\n\n**Disclaimer:** Most of the files here are maintained in a "
  },
  {
    "path": "dist/apparmor/dev.foxcpp.maddy",
    "chars": 980,
    "preview": "# AppArmor profile for maddy daemon.\n# vim:syntax=apparmor:ts=2:sw=2:et\n\n#include <tunables/global>\n\nprofile dev.foxcpp."
  },
  {
    "path": "dist/fail2ban/filter.d/maddy-auth.conf",
    "chars": 213,
    "preview": "[INCLUDES]\nbefore = common.conf\n\n[Definition]\nfailregex    = authentication failed\\t\\{\\\"reason\\\":\\\".*\\\",\\\"src_ip\\\"\\:\\\"<H"
  },
  {
    "path": "dist/fail2ban/filter.d/maddy-dictonary-attack.conf",
    "chars": 367,
    "preview": "[INCLUDES]\nbefore = common.conf\n\n[Definition]\nfailregex    = smtp\\: MAIL FROM error repeated a lot\\, possible dictonary "
  },
  {
    "path": "dist/fail2ban/jail.d/maddy-auth.conf",
    "chars": 91,
    "preview": "[maddy-auth]\nport     = 993,465,25\nfilter   = maddy-auth\nbantime  = 96h\nbackend  = systemd\n"
  },
  {
    "path": "dist/fail2ban/jail.d/maddy-dictonary-attack.conf",
    "chars": 142,
    "preview": "[maddy-dictonary-attack]\nport     = 993,465,25\nfilter   = maddy-dictonary-attack\nbantime  = 72h\nmaxretry = 3\nfindtime = "
  },
  {
    "path": "dist/install.sh",
    "chars": 828,
    "preview": "#!/bin/bash\n\nDESTDIR=$DESTDIR\nif [ -z \"$PREFIX\" ]; then\n    PREFIX=/usr/local\nfi\nif [ -z \"$FAIL2BANDIR\" ]; then\n    FAIL"
  },
  {
    "path": "dist/logrotate.d/maddy",
    "chars": 128,
    "preview": "/var/log/maddy/maddy.log {\n    missingok\n    su maddy maddy\n    postrotate\n        /usr/bin/killall -USR1 maddy\n    ends"
  },
  {
    "path": "dist/systemd/maddy.service",
    "chars": 2057,
    "preview": "[Unit]\nDescription=maddy mail server\nDocumentation=man:maddy(1)\nDocumentation=man:maddy.conf(5)\nDocumentation=https://ma"
  },
  {
    "path": "dist/systemd/maddy@.service",
    "chars": 1951,
    "preview": "[Unit]\nDescription=maddy mail server (using %i.conf)\nDocumentation=man:maddy(1)\nDocumentation=man:maddy.conf(5)\nDocument"
  },
  {
    "path": "dist/vim/ftdetect/maddy-conf.vim",
    "chars": 62,
    "preview": "au BufNewFile,BufRead /etc/maddy/*,maddy.conf setf maddy-conf\n"
  },
  {
    "path": "dist/vim/ftplugin/maddy-conf.vim",
    "chars": 194,
    "preview": "setlocal commentstring=#\\ %s\n\n\" That is convention for maddy configs. Period.\n\"    - fox.cpp (maddy developer)\nsetlocal "
  },
  {
    "path": "dist/vim/syntax/maddy-conf.vim",
    "chars": 3956,
    "preview": "\" vim: noexpandtab ts=4 sw=4\n\nif exists(\"b:current_syntax\")\n\tfinish\nendif\n\n\" Lexer-defined rules\nsyn match\t\tmaddyComment"
  },
  {
    "path": "docs/docker.md",
    "chars": 2909,
    "preview": "# Docker\n\nOfficial Docker image is available from Docker Hub.\n\nIt expects configuration file to be available at /data/ma"
  },
  {
    "path": "docs/faq.md",
    "chars": 4092,
    "preview": "# Frequently Asked Questions\n\n## I configured maddy as recommended and gmail still puts my messages in spam\n\nUnfortunate"
  },
  {
    "path": "docs/index.md",
    "chars": 1260,
    "preview": "> Composable all-in-one mail server.\n\nMaddy Mail Server implements all functionality required to run a e-mail\nserver. It"
  },
  {
    "path": "docs/internals/quirks.md",
    "chars": 556,
    "preview": "# Implementation quirks\n\nThis page documents unusual behavior of the maddy protocols implementations.\nSome of these prob"
  },
  {
    "path": "docs/internals/specifications.md",
    "chars": 12719,
    "preview": "# Followed specifications\n\nThis page lists Internet Standards and other specifications followed by\nmaddy along with any "
  },
  {
    "path": "docs/internals/sqlite.md",
    "chars": 1941,
    "preview": "# maddy & SQLite\n\nSQLite is a perfect choice for small deployments because no additional\nconfiguration is required to ge"
  },
  {
    "path": "docs/internals/unicode.md",
    "chars": 3857,
    "preview": "# Unicode support\n\nmaddy has the first-class Unicode support in all components (modules). You do\nnot have to take any ac"
  },
  {
    "path": "docs/man/.gitignore",
    "chars": 16,
    "preview": "_generated_*.md\n"
  },
  {
    "path": "docs/man/README.md",
    "chars": 499,
    "preview": "maddy manual pages\n-------------------\n\nThe reference documentation is maintained in the scdoc format and is compiled\nin"
  },
  {
    "path": "docs/man/maddy.1.scd",
    "chars": 1050,
    "preview": "maddy(1) \"maddy mail server\" \"maddy reference documentation\"\n\n; TITLE Command line arguments\n\n# Name\n\nmaddy - Composable"
  },
  {
    "path": "docs/man/prepare_md.py",
    "chars": 1628,
    "preview": "#!/usr/bin/python3\n\n\"\"\"\nThis script does all necessary pre-processing to convert scdoc format into\nMarkdown.\n\nUsage:\n   "
  },
  {
    "path": "docs/multiple-domains.md",
    "chars": 4774,
    "preview": "# Multiple domains configuration\n\nBy default, maddy uses email addresses as account identifiers for both\nauthentication "
  },
  {
    "path": "docs/reference/auth/dovecot_sasl.md",
    "chars": 639,
    "preview": "# Dovecot SASL\n\nThe 'auth.dovecot_sasl' module implements the client side of the Dovecot\nauthentication protocol, allowi"
  },
  {
    "path": "docs/reference/auth/external.md",
    "chars": 1543,
    "preview": "# System command\n\nauth.external module for authentication using external helper binary. It looks for binary\nnamed `maddy"
  },
  {
    "path": "docs/reference/auth/ldap.md",
    "chars": 2906,
    "preview": "# LDAP BindDN\n\nmaddy supports authentication via LDAP using DN binding. Passwords are verified\nby the LDAP server.\n\nmadd"
  },
  {
    "path": "docs/reference/auth/netauth.md",
    "chars": 1425,
    "preview": "# Native NetAuth\n\nmaddy supports authentication via NetAuth using direct entity\nauthentication checks.  Passwords are ve"
  },
  {
    "path": "docs/reference/auth/pam.md",
    "chars": 1115,
    "preview": "# PAM\n\nauth.pam module implements authentication using libpam. Alternatively it can be configured to\nuse helper binary l"
  },
  {
    "path": "docs/reference/auth/pass_table.md",
    "chars": 1146,
    "preview": "# Password table\n\nauth.pass_table module implements username:password authentication by looking up the\npassword hash usi"
  },
  {
    "path": "docs/reference/auth/plain_separate.md",
    "chars": 1110,
    "preview": "# Separate username and password lookup\n\nauth.plain_separate module implements authentication using username:password pa"
  },
  {
    "path": "docs/reference/auth/shadow.md",
    "chars": 899,
    "preview": "# /etc/shadow\n\nauth.shadow module implements authentication by reading /etc/shadow. Alternatively it can be\nconfigured t"
  },
  {
    "path": "docs/reference/blob/fs.md",
    "chars": 466,
    "preview": "# Filesystem\n\nThis module stores message bodies in a file system directory.\n\n```\nstorage.blob.fs {\n    root <directory>\n"
  },
  {
    "path": "docs/reference/blob/s3.md",
    "chars": 1767,
    "preview": "# Amazon S3\n\nstorage.blob.s3 module stores messages bodies in a bucket on S3-compatible storage.\n\n```\nstorage.blob.s3 {\n"
  },
  {
    "path": "docs/reference/checks/actions.md",
    "chars": 800,
    "preview": "# Check actions\n\nWhen a certain check module thinks the message is \"bad\", it takes some actions\ndepending on its configu"
  },
  {
    "path": "docs/reference/checks/authorize_sender.md",
    "chars": 3471,
    "preview": "# MAIL FROM and From authorization\n\nModule check.authorize_sender verifies that envelope and header sender addresses bel"
  },
  {
    "path": "docs/reference/checks/command.md",
    "chars": 3602,
    "preview": "# System command filter\n\nThis module executes an arbitrary system command during a specified stage of\nchecks execution.\n"
  },
  {
    "path": "docs/reference/checks/dkim.md",
    "chars": 1502,
    "preview": "# DKIM\n\nThis is the check module that performs verification of the DKIM signatures\npresent on the incoming messages.\n\n##"
  },
  {
    "path": "docs/reference/checks/dnsbl.md",
    "chars": 6284,
    "preview": "# DNSBL lookup\n\nThe check.dnsbl module implements checking of source IP and hostnames against a set\nof DNS-based Blackho"
  },
  {
    "path": "docs/reference/checks/milter.md",
    "chars": 1391,
    "preview": "# Milter client\n\nThe 'milter' implements subset of Sendmail's milter protocol that can be used\nto integrate external sof"
  },
  {
    "path": "docs/reference/checks/misc.md",
    "chars": 1184,
    "preview": "# Misc checks\n\n## Configuration directives\n\nFollowing directives are defined for all modules listed below.\n\n### fail_act"
  },
  {
    "path": "docs/reference/checks/rspamd.md",
    "chars": 2112,
    "preview": "# rspamd\n\nThe 'rspamd' module implements message filtering by contacting the rspamd\nserver via HTTP API.\n\n```\ncheck.rspa"
  },
  {
    "path": "docs/reference/checks/spf.md",
    "chars": 2587,
    "preview": "# SPF\n\ncheck.spf the check module that verifies whether IP address of the client is\nauthorized to send messages for doma"
  },
  {
    "path": "docs/reference/config-syntax.md",
    "chars": 4573,
    "preview": "# Configuration files syntax\n\n**Note:** This file is a technical document describing how\nmaddy parses configuration file"
  },
  {
    "path": "docs/reference/endpoints/imap.md",
    "chars": 3701,
    "preview": "# IMAP4rev1 endpoint\n\nModule 'imap' is a listener that implements IMAP4rev1 protocol and provides\naccess to local messag"
  },
  {
    "path": "docs/reference/endpoints/openmetrics.md",
    "chars": 1487,
    "preview": "# OpenMetrics/Prometheus telemetry\n\nVarious server statistics are provided in OpenMetrics format by the\n\"openmetrics\" mo"
  },
  {
    "path": "docs/reference/endpoints/smtp.md",
    "chars": 7712,
    "preview": "# SMTP/LMTP/Submission endpoint\n\nModule 'smtp' is a listener that implements ESMTP protocol with optional\nauthentication"
  },
  {
    "path": "docs/reference/global-config.md",
    "chars": 4477,
    "preview": "# Global configuration directives\n\nThese directives can be specified outside of any\nconfiguration blocks and they are ap"
  },
  {
    "path": "docs/reference/modifiers/dkim.md",
    "chars": 5477,
    "preview": "# DKIM signing\n\nmodify.dkim module is a modifier that signs messages using DKIM\nprotocol (RFC 6376).\n\nEach configuration"
  },
  {
    "path": "docs/reference/modifiers/envelope.md",
    "chars": 1909,
    "preview": "# Envelope sender / recipient rewriting\n\n`replace_sender` and `replace_rcpt` modules replace SMTP envelope addresses\nbas"
  },
  {
    "path": "docs/reference/modules.md",
    "chars": 2457,
    "preview": "# Modules introduction\n\nmaddy is built of many small components called \"modules\". Each module does one\ncertain well-defi"
  },
  {
    "path": "docs/reference/smtp-pipeline.md",
    "chars": 11245,
    "preview": "# SMTP message routing (pipeline)\n\n# Message pipeline\n\nA message pipeline is a set of module references and associated r"
  },
  {
    "path": "docs/reference/storage/imap-filters.md",
    "chars": 2485,
    "preview": "# IMAP filters\n\nMost storage backends support application of custom code late in delivery\nprocess. As opposed to using S"
  },
  {
    "path": "docs/reference/storage/imapsql.md",
    "chars": 5013,
    "preview": "# SQL-indexed storage\n\nThe imapsql module implements database for IMAP index and message\nmetadata using SQL-based relati"
  },
  {
    "path": "docs/reference/table/auth.md",
    "chars": 248,
    "preview": "# Authentication providers\n\nMost authentication providers are also usable as a table\nthat contains all usernames known t"
  },
  {
    "path": "docs/reference/table/chain.md",
    "chars": 879,
    "preview": "# Table chaining\n\nThe table.chain module allows chaining together multiple table modules\nby using value returned by a pr"
  },
  {
    "path": "docs/reference/table/email_localpart.md",
    "chars": 519,
    "preview": "# Email local part\n\nThe module `table.email_localpart` extracts and unescapes local (\"username\") part\nof the email addre"
  },
  {
    "path": "docs/reference/table/email_with_domain.md",
    "chars": 939,
    "preview": "# Email with domain\n\nThe table module `table.email_with_domain` appends one or more\ndomains (allowing 1:N expansion) to "
  },
  {
    "path": "docs/reference/table/file.md",
    "chars": 1141,
    "preview": "# File \n\ntable.file module builds string-string mapping from a text file.\n\nFile is reloaded every 15 seconds if there ar"
  },
  {
    "path": "docs/reference/table/regexp.md",
    "chars": 1599,
    "preview": "# Regexp rewrite table\n\nThe 'regexp' module implements table lookups by applying a regular expression\nto the key value. "
  },
  {
    "path": "docs/reference/table/sql_query.md",
    "chars": 3095,
    "preview": "# SQL query mapping\n\nThe table.sql_query module implements table interface using SQL queries.\n\nDefinition:\n\n```\ntable.sq"
  },
  {
    "path": "docs/reference/table/static.md",
    "chars": 325,
    "preview": "# Static table\n\nThe 'static' module implements table lookups using key-value pairs in its\nconfiguration.\n\n```\ntable.stat"
  },
  {
    "path": "docs/reference/targets/queue.md",
    "chars": 2159,
    "preview": "# Local queue\n\nQueue module buffers messages on disk and retries delivery multiple times to\nanother target to ensure rel"
  },
  {
    "path": "docs/reference/targets/remote.md",
    "chars": 7252,
    "preview": "# Remote MX delivery\n\nModule that implements message delivery to remote MTAs discovered via DNS MX\nrecords. You probably"
  },
  {
    "path": "docs/reference/targets/smtp.md",
    "chars": 2885,
    "preview": "# SMTP & LMTP transparent forwarding\n\nModule that implements transparent forwarding of messages over SMTP.\n\nUse in pipel"
  },
  {
    "path": "docs/reference/tls-acme.md",
    "chars": 4912,
    "preview": "# Automatic certificate management via ACME\n\nMaddy supports obtaining certificates using ACME protocol.\n\nTo use it, crea"
  },
  {
    "path": "docs/reference/tls.md",
    "chars": 3987,
    "preview": "# TLS configuration\n\n## Server-side\n\nTLS certificates are obtained by modules called \"certificate loaders\". 'tls' direct"
  },
  {
    "path": "docs/seclevels.md",
    "chars": 3818,
    "preview": "# Outbound delivery security\n\nmaddy implements a number of schemes and protocols for discovery and\nenforcement of securi"
  },
  {
    "path": "docs/third-party/dovecot.md",
    "chars": 2311,
    "preview": "# Dovecot\n\nBuiltin maddy IMAP server may not match your requirements in terms of\nperformance, reliability or anything. F"
  },
  {
    "path": "docs/third-party/mailman3.md",
    "chars": 2574,
    "preview": "# Mailman 3\n\nSetting up Mailman 3 with maddy involves some additional work as compared to\nother MTAs as there is no Pyth"
  },
  {
    "path": "docs/third-party/rspamd.md",
    "chars": 916,
    "preview": "# rspamd\n\nmaddy has direct support for rspamd HTTP protocol. There is no need to use\nmilter proxy.\n\nIf rspamd is running"
  },
  {
    "path": "docs/third-party/smtp-servers.md",
    "chars": 1547,
    "preview": "# External SMTP server\n\nIt is possible to use maddy as an IMAP server only and have it interface with\nexternal SMTP serv"
  },
  {
    "path": "docs/tutorials/alias-to-remote.md",
    "chars": 4133,
    "preview": "# Forward messages to a remote MX\n\nDefault maddy configuration is done in a way that does not result in any\noutbound mes"
  },
  {
    "path": "docs/tutorials/building-from-source.md",
    "chars": 1371,
    "preview": "# Building from source\n\n## System dependencies\n\nYou need C toolchain, Go toolchain and Make:\n\nOn Debian-based system thi"
  },
  {
    "path": "docs/tutorials/pam.md",
    "chars": 2463,
    "preview": "# Using PAM authentication\n\nmaddy supports user authentication using PAM infrastructure via `auth.pam`\nmodule.\n\nIn order"
  },
  {
    "path": "docs/tutorials/setting-up.md",
    "chars": 9235,
    "preview": "# Installation & initial configuration\n\nThis is the practical guide on how to set up a mail server using maddy for\nperso"
  },
  {
    "path": "docs/upgrading.md",
    "chars": 2383,
    "preview": "# Upgrading from older maddy versions\n\nIt is generally possible to just install latest version (e.g. using build.sh\nscri"
  },
  {
    "path": "framework/address/doc.go",
    "chars": 872,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/norm.go",
    "chars": 4552,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/norm_test.go",
    "chars": 2977,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/rfc6531.go",
    "chars": 2218,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/rfc6531_test.go",
    "chars": 1511,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/split.go",
    "chars": 3437,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/split_test.go",
    "chars": 3092,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/validation.go",
    "chars": 3282,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/address/validation_test.go",
    "chars": 995,
    "preview": "package address_test\n\nimport (\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/foxcpp/maddy/framework/address\"\n)\n\nfunc TestValidMail"
  },
  {
    "path": "framework/buffer/buffer.go",
    "chars": 2458,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/buffer/bytesreader.go",
    "chars": 1883,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/buffer/file.go",
    "chars": 2395,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/buffer/memory.go",
    "chars": 1387,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/cfgparser/env.go",
    "chars": 1918,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/cfgparser/imports.go",
    "chars": 4527,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/cfgparser/parse.go",
    "chars": 10457,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/cfgparser/parse_test.go",
    "chars": 10068,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/config.go",
    "chars": 1086,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/directories.go",
    "chars": 1659,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/endpoint.go",
    "chars": 3219,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/endpoint_test.go",
    "chars": 2258,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/lexer/LICENSE.APACHE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "framework/config/lexer/README.md",
    "chars": 616,
    "preview": "caddyfile lexer copied from [caddy](https://github.com/caddyserver/caddy) project.\n\nTaken from the following commit:\n```"
  },
  {
    "path": "framework/config/lexer/dispenser.go",
    "chars": 7827,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/lexer/dispenser_test.go",
    "chars": 9499,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/lexer/lexer.go",
    "chars": 4052,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/lexer/lexer_test.go",
    "chars": 4942,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/lexer/parse.go",
    "chars": 1211,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/map.go",
    "chars": 22507,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/map_test.go",
    "chars": 9623,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/module/check_action.go",
    "chars": 5148,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/module/interfaces.go",
    "chars": 3362,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/module/modconfig.go",
    "chars": 6233,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/tls/client.go",
    "chars": 2599,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/tls/general.go",
    "chars": 5331,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/config/tls/server.go",
    "chars": 3435,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/container/container.go",
    "chars": 2176,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/container/lifetime.go",
    "chars": 4138,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2025 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/container/registry.go",
    "chars": 3598,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/dns/debugflags.go",
    "chars": 1278,
    "preview": "//go:build debugflags\n// +build debugflags\n\n/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-"
  },
  {
    "path": "framework/dns/dnssec.go",
    "chars": 10096,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/dns/dnssec_test.go",
    "chars": 5243,
    "preview": "package dns\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net\"\n\t\"reflect\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/foxcpp/maddy/frame"
  },
  {
    "path": "framework/dns/idna.go",
    "chars": 1254,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/dns/norm.go",
    "chars": 2285,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/dns/override.go",
    "chars": 1787,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/dns/resolver.go",
    "chars": 2031,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/exterrors/dns.go",
    "chars": 1171,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/exterrors/exterrors.go",
    "chars": 935,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/exterrors/fields.go",
    "chars": 1727,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/exterrors/smtp.go",
    "chars": 4486,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/exterrors/temporary.go",
    "chars": 2025,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/future/future.go",
    "chars": 2274,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/future/future_test.go",
    "chars": 1821,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/hooks/hooks.go",
    "chars": 2415,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/log/log.go",
    "chars": 7580,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/log/orderedjson.go",
    "chars": 2067,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/log/output.go",
    "chars": 1736,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/log/syslog.go",
    "chars": 1663,
    "preview": "//go:build !windows && !plan9\n// +build !windows,!plan9\n\n/*\nMaddy Mail Server - Composable all-in-one email server.\nCopy"
  },
  {
    "path": "framework/log/syslog_stub.go",
    "chars": 1241,
    "preview": "//go:build windows || plan9\n// +build windows plan9\n\n/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyrigh"
  },
  {
    "path": "framework/log/writer.go",
    "chars": 3020,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/log/zap.go",
    "chars": 1187,
    "preview": "package log\n\nimport (\n\t\"go.uber.org/zap/zapcore\"\n)\n\n// TODO: Migrate to using actual zapcore to improve logging performa"
  },
  {
    "path": "framework/logparser/parse.go",
    "chars": 3270,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/logparser/parse_test.go",
    "chars": 3593,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/auth.go",
    "chars": 1599,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/blob_store.go",
    "chars": 1333,
    "preview": "package module\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n)\n\ntype Blob interface {\n\tSync() error\n\tio.Writer\n\tio.Closer\n}\n\nvar "
  },
  {
    "path": "framework/module/check.go",
    "chars": 4201,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/delivery_target.go",
    "chars": 3783,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/imap_filter.go",
    "chars": 1760,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/modifier.go",
    "chars": 3413,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/module.go",
    "chars": 1979,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  },
  {
    "path": "framework/module/module_specific_data.go",
    "chars": 1672,
    "preview": "package module\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"sync\"\n)\n\n// ModSpecificData is a container that allows modules to att"
  },
  {
    "path": "framework/module/modules/dummy.go",
    "chars": 2515,
    "preview": "/*\nMaddy Mail Server - Composable all-in-one email server.\nCopyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Madd"
  }
]

// ... and 248 more files (download for full content)

About this extraction

This page contains the full source code of the emersion/maddy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 448 files (1.8 MB), approximately 569.4k tokens, and a symbol index with 2069 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!