real multi-domain support

This commit is contained in:
Aine
2022-11-16 09:00:19 +02:00
parent f3be3aeabb
commit ebe9606aa9
9 changed files with 233 additions and 174 deletions

155
README.md
View File

@@ -19,7 +19,7 @@ so you can use it to send emails from your apps and scripts as well.
- [x] Receive attachments - [x] Receive attachments
- [x] Catch-all mailbox - [x] Catch-all mailbox
- [x] Map email threads to matrix threads - [x] Map email threads to matrix threads
- [x] Multi-domain aliases - [x] Multi-domain support
#### deep dive #### deep dive
@@ -46,7 +46,7 @@ env vars
* **POSTMOOGLE_HOMESERVER** - homeserver url, eg: `https://matrix.example.com` * **POSTMOOGLE_HOMESERVER** - homeserver url, eg: `https://matrix.example.com`
* **POSTMOOGLE_LOGIN** - user login/localpart, eg: `moogle` * **POSTMOOGLE_LOGIN** - user login/localpart, eg: `moogle`
* **POSTMOOGLE_PASSWORD** - user password * **POSTMOOGLE_PASSWORD** - user password
* **POSTMOOGLE_DOMAINS** - space separated list of SMTP domains to listen for new emails. The first domain acts as the main (actual) domain, all other as aliases * **POSTMOOGLE_DOMAINS** - space separated list of SMTP domains to listen for new emails. The first domain acts as the default domain, all other as aliases
<details> <details>
<summary>other optional config parameters</summary> <summary>other optional config parameters</summary>
@@ -72,155 +72,7 @@ You can find default values in [config/defaults.go](config/defaults.go)
### 2. DNS (optional) ### 2. DNS (optional)
The following configuration is needed only if you want to send outgoing emails via Postmoogle (it's not necessary if you only want to receive emails). Follow the [docs/dns](docs/dns.md)
<details>
<summary>TL;DR</summary>
1. Configure DMARC record
2. Configure SPF record
3. Configure MX record
4. Configure DKIM record (use `!pm dkim`)
</details>
**First**, add a new DMARC DNS record of the `TXT` type for subdomain `_dmarc` with a proper policy. The simplest policy you can use is: `v=DMARC1; p=quarantine;`.
<details>
<summary>Example</summary>
```bash
$ dig txt _dmarc.example.com
; <<>> DiG 9.18.6 <<>> txt _dmarc.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57306
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_dmarc.example.com. IN TXT
;; ANSWER SECTION:
_dmarc.example.com. 1799 IN TXT "v=DMARC1; p=quarantine;"
;; Query time: 46 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 04 21:31:30 EEST 2022
;; MSG SIZE rcvd: 79
```
</details>
**Second**, add a new SPF DNS record of the `TXT` type for your domain that will be used with Postmoogle, with format: `v=spf1 ip4:SERVER_IP -all` (replace `SERVER_IP` with your server's IP address)
<details>
<summary>Example</summary>
```bash
$ dig txt example.com
; <<>> DiG 9.18.6 <<>> txt example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24796
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN TXT
;; ANSWER SECTION:
example.com. 1799 IN TXT "v=spf1 ip4:111.111.111.111 -all"
;; Query time: 36 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 04 21:35:04 EEST 2022
;; MSG SIZE rcvd: 255
```
</details>
**Third**, add a new MX DNS record of the `MX` type for your domain that will be used with postmoogle. It should point to the same (sub-)domain.
Looks odd, but some mail servers will refuse to interact with your mail server (and Postmoogle is already a mail server) without MX records.
<details>
<summary>Example</summary>
```bash
dig MX example.com
; <<>> DiG 9.18.6 <<>> MX example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12688
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN MX
;; ANSWER SECTION:
example.com. 1799 IN MX 10 example.com.
;; Query time: 40 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Tue Sep 06 16:44:47 EEST 2022
;; MSG SIZE rcvd: 59
```
</details>
**Fourth** (and the last one), add new DKIM DNS record of `TXT` type for subdomain `postmoogle._domainkey` that will be used with postmoogle.
You can get that signature using the `!pm dkim` command:
<details>
<summary>!pm dkim</summary>
DKIM signature is: `v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE=`.
You need to add it to your DNS records (if not already):
Add new DNS record with type = `TXT`, key (subdomain/from): `postmoogle._domainkey` and value (to):
```
v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE=
```
Without that record other email servers may reject your emails as spam, kupo.
</details>
<details>
<summary>Example</summary>
```bash
$ dig TXT postmoogle._domainkey.example.com
; <<>> DiG 9.18.6 <<>> TXT postmoogle._domainkey.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59014
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;postmoogle._domainkey.example.com. IN TXT
;; ANSWER SECTION:
postmoogle._domainkey.example.com. 600 IN TXT "v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE="
;; Query time: 90 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Sep 05 16:16:21 EEST 2022
;; MSG SIZE rcvd: 525
```
</details>
## Usage ## Usage
@@ -241,6 +93,7 @@ If you want to change them - check available options in the help message (`!pm h
--- ---
* **!pm mailbox** - Get or set mailbox of the room * **!pm mailbox** - Get or set mailbox of the room
* **!pm domain** - Get or set default domain of the room
* **!pm owner** - Get or set owner of the room * **!pm owner** - Get or set owner of the room
* **!pm password** - Get or set SMTP password of the room's mailbox * **!pm password** - Get or set SMTP password of the room's mailbox

View File

@@ -70,6 +70,12 @@ func (b *Bot) initCommands() commandList {
sanitizer: utils.Mailbox, sanitizer: utils.Mailbox,
allowed: b.allowOwner, allowed: b.allowOwner,
}, },
{
key: roomOptionDomain,
description: "Get or set default domain of the room",
sanitizer: utils.SanitizeDomain,
allowed: b.allowOwner,
},
{ {
key: roomOptionOwner, key: roomOptionOwner,
description: "Get or set owner of the room", description: "Get or set owner of the room",
@@ -276,7 +282,7 @@ func (b *Bot) sendIntroduction(ctx context.Context, roomID id.RoomID) {
msg.WriteString(" SOME_INBOX` command.\n") msg.WriteString(" SOME_INBOX` command.\n")
msg.WriteString("You will then be able to send emails to ") msg.WriteString("You will then be able to send emails to ")
msg.WriteString(utils.EmailsList("SOME_INBOX", b.domains)) msg.WriteString(utils.EmailsList("SOME_INBOX", ""))
msg.WriteString("` and have them appear in this room.") msg.WriteString("` and have them appear in this room.")
b.SendNotice(ctx, roomID, msg.String()) b.SendNotice(ctx, roomID, msg.String())
@@ -315,7 +321,7 @@ func (b *Bot) sendHelp(ctx context.Context) {
msg.WriteString(value) msg.WriteString(value)
if cmd.key == roomOptionMailbox { if cmd.key == roomOptionMailbox {
msg.WriteString(" (") msg.WriteString(" (")
msg.WriteString(utils.EmailsList(value, b.domains)) msg.WriteString(utils.EmailsList(value, cfg.Domain()))
msg.WriteString(")") msg.WriteString(")")
} }
msg.WriteString("`)") msg.WriteString("`)")
@@ -376,8 +382,9 @@ func (b *Bot) runSend(ctx context.Context) {
b.lock(evt.RoomID.String()) b.lock(evt.RoomID.String())
defer b.unlock(evt.RoomID.String()) defer b.unlock(evt.RoomID.String())
from := mailbox + "@" + b.domains[0] domain := utils.SanitizeDomain(cfg.Domain())
ID := utils.MessageID(evt.ID, b.domains[0]) from := mailbox + "@" + domain
ID := utils.MessageID(evt.ID, domain)
for _, to := range tos { for _, to := range tos {
email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, "", nil) email := utils.NewEmail(ID, "", " "+ID, subject, from, to, body, "", nil)
data := email.Compose(b.getBotSettings().DKIMPrivateKey()) data := email.Compose(b.getBotSettings().DKIMPrivateKey())

View File

@@ -53,7 +53,7 @@ func (b *Bot) sendMailboxes(ctx context.Context) {
for _, mailbox := range slice { for _, mailbox := range slice {
cfg := mailboxes[mailbox] cfg := mailboxes[mailbox]
msg.WriteString("* `") msg.WriteString("* `")
msg.WriteString(utils.EmailsList(mailbox, b.domains)) msg.WriteString(utils.EmailsList(mailbox, cfg.Domain()))
msg.WriteString("` by ") msg.WriteString("` by ")
msg.WriteString(cfg.Owner()) msg.WriteString(cfg.Owner())
msg.WriteString("\n") msg.WriteString("\n")
@@ -174,7 +174,7 @@ func (b *Bot) runCatchAll(ctx context.Context, commandSlice []string) {
if cfg.CatchAll() != "" { if cfg.CatchAll() != "" {
msg.WriteString(cfg.CatchAll()) msg.WriteString(cfg.CatchAll())
msg.WriteString(" (") msg.WriteString(" (")
msg.WriteString(utils.EmailsList(cfg.CatchAll(), b.domains)) msg.WriteString(utils.EmailsList(cfg.CatchAll(), ""))
msg.WriteString(")") msg.WriteString(")")
} else { } else {
msg.WriteString("not set") msg.WriteString("not set")
@@ -203,5 +203,5 @@ func (b *Bot) runCatchAll(ctx context.Context, commandSlice []string) {
return return
} }
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Catch-all is set to: `%s` (%s).", mailbox, utils.EmailsList(mailbox, b.domains))) b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Catch-all is set to: `%s` (%s).", mailbox, utils.EmailsList(mailbox, "")))
} }

View File

@@ -60,7 +60,7 @@ func (b *Bot) getOption(ctx context.Context, name string) {
} }
if name == roomOptionMailbox { if name == roomOptionMailbox {
value = utils.EmailsList(value, b.domains) value = utils.EmailsList(value, cfg.Domain())
} }
msg := fmt.Sprintf("`%s` of this room is `%s`\n"+ msg := fmt.Sprintf("`%s` of this room is `%s`\n"+
@@ -87,7 +87,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
if name == roomOptionMailbox { if name == roomOptionMailbox {
existingID, ok := b.getMapping(value) existingID, ok := b.getMapping(value)
if ok && existingID != "" && existingID != evt.RoomID { if ok && existingID != "" && existingID != evt.RoomID {
b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s` (%s) already taken, kupo", value, utils.EmailsList(value, b.domains))) b.SendNotice(ctx, evt.RoomID, fmt.Sprintf("Mailbox `%s` (%s) already taken, kupo", value, utils.EmailsList(value, "")))
return return
} }
} }
@@ -116,7 +116,7 @@ func (b *Bot) setOption(ctx context.Context, name, value string) {
b.rooms.Delete(old) b.rooms.Delete(old)
} }
b.rooms.Store(value, evt.RoomID) b.rooms.Store(value, evt.RoomID)
value = fmt.Sprintf("%s@%s", value, b.domains[0]) value = fmt.Sprintf("%s@%s", value, utils.SanitizeDomain(cfg.Domain()))
} }
err = b.setRoomSettings(evt.RoomID, cfg) err = b.setRoomSettings(evt.RoomID, cfg)

View File

@@ -147,12 +147,13 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
b.Error(ctx, evt.RoomID, "mailbox is not configured, kupo") b.Error(ctx, evt.RoomID, "mailbox is not configured, kupo")
return return
} }
domain := utils.SanitizeDomain(cfg.Domain())
b.lock(evt.RoomID.String()) b.lock(evt.RoomID.String())
defer b.unlock(evt.RoomID.String()) defer b.unlock(evt.RoomID.String())
fromMailbox := mailbox + "@" + b.domains[0] fromMailbox := mailbox + "@" + domain
meta := b.getParentEmail(evt) meta := b.getParentEmail(evt, domain)
// when email was sent from matrix and reply was sent from matrix again // when email was sent from matrix and reply was sent from matrix again
if fromMailbox != meta.From { if fromMailbox != meta.From {
meta.To = meta.From meta.To = meta.From
@@ -173,7 +174,7 @@ func (b *Bot) SendEmailReply(ctx context.Context) {
} }
body := content.Body body := content.Body
meta.MessageID = utils.MessageID(evt.ID, b.domains[0]) meta.MessageID = utils.MessageID(evt.ID, domain)
meta.References = meta.References + " " + meta.MessageID meta.References = meta.References + " " + meta.MessageID
b.log.Debug("send email reply: %+v", meta) b.log.Debug("send email reply: %+v", meta)
email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, "", nil) email := utils.NewEmail(meta.MessageID, meta.InReplyTo, meta.References, meta.Subject, meta.From, meta.To, body, "", nil)
@@ -240,7 +241,7 @@ func (b *Bot) getParentEvent(evt *event.Event) (id.EventID, *event.Event) {
return threadID, decrypted return threadID, decrypted
} }
func (b *Bot) getParentEmail(evt *event.Event) parentEmail { func (b *Bot) getParentEmail(evt *event.Event, domain string) parentEmail {
var parent parentEmail var parent parentEmail
threadID, parentEvt := b.getParentEvent(evt) threadID, parentEvt := b.getParentEvent(evt)
parent.ThreadID = threadID parent.ThreadID = threadID
@@ -251,7 +252,7 @@ func (b *Bot) getParentEmail(evt *event.Event) parentEmail {
return parent return parent
} }
parent.MessageID = utils.MessageID(parentEvt.ID, b.domains[0]) parent.MessageID = utils.MessageID(parentEvt.ID, domain)
parent.From = utils.EventField[string](&parentEvt.Content, eventFromKey) parent.From = utils.EventField[string](&parentEvt.Content, eventFromKey)
parent.To = utils.EventField[string](&parentEvt.Content, eventToKey) parent.To = utils.EventField[string](&parentEvt.Content, eventToKey)
parent.InReplyTo = utils.EventField[string](&parentEvt.Content, eventMessageIDkey) parent.InReplyTo = utils.EventField[string](&parentEvt.Content, eventMessageIDkey)
@@ -298,8 +299,9 @@ func (b *Bot) saveSentMetadata(ctx context.Context, queued bool, threadID id.Eve
b.Error(ctx, evt.RoomID, "cannot send notice: %v", err) b.Error(ctx, evt.RoomID, "cannot send notice: %v", err)
return return
} }
b.setThreadID(evt.RoomID, utils.MessageID(evt.ID, b.domains[0]), threadID) domain := utils.SanitizeDomain(cfg.Domain())
b.setThreadID(evt.RoomID, utils.MessageID(msgID, b.domains[0]), threadID) b.setThreadID(evt.RoomID, utils.MessageID(evt.ID, domain), threadID)
b.setThreadID(evt.RoomID, utils.MessageID(msgID, domain), threadID)
b.setLastEventID(evt.RoomID, threadID, msgID) b.setLastEventID(evt.RoomID, threadID, msgID)
} }

View File

@@ -15,6 +15,7 @@ const acRoomSettingsKey = "cc.etke.postmoogle.settings"
const ( const (
roomOptionOwner = "owner" roomOptionOwner = "owner"
roomOptionMailbox = "mailbox" roomOptionMailbox = "mailbox"
roomOptionDomain = "domain"
roomOptionNoSend = "nosend" roomOptionNoSend = "nosend"
roomOptionNoSender = "nosender" roomOptionNoSender = "nosender"
roomOptionNoRecipient = "norecipient" roomOptionNoRecipient = "norecipient"
@@ -44,6 +45,10 @@ func (s roomSettings) Mailbox() string {
return s.Get(roomOptionMailbox) return s.Get(roomOptionMailbox)
} }
func (s roomSettings) Domain() string {
return s.Get(roomOptionDomain)
}
func (s roomSettings) Owner() string { func (s roomSettings) Owner() string {
return s.Get(roomOptionOwner) return s.Get(roomOptionOwner)
} }

View File

@@ -34,6 +34,7 @@ func main() {
cfg := config.New() cfg := config.New()
log = logger.New("postmoogle.", cfg.LogLevel) log = logger.New("postmoogle.", cfg.LogLevel)
utils.SetLogger(log) utils.SetLogger(log)
utils.SetDomains(cfg.Domains)
log.Info("#############################") log.Info("#############################")
log.Info("Postmoogle") log.Info("Postmoogle")

159
docs/dns.md Normal file
View File

@@ -0,0 +1,159 @@
# DNS configuration
the following configuration is required only if you want to send emails from Postmoogle
# MX
Add a new MX DNS record of the `MX` type for your domain that will be used with postmoogle.
It should point to the same (sub-)domain.
Looks odd, but some mail servers will refuse to interact with your mail server
(and Postmoogle is already a mail server) without MX records.
<details>
<summary>Example</summary>
```bash
dig MX example.com
; <<>> DiG 9.18.6 <<>> MX example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12688
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN MX
;; ANSWER SECTION:
example.com. 1799 IN MX 10 example.com.
;; Query time: 40 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Tue Sep 06 16:44:47 EEST 2022
;; MSG SIZE rcvd: 59
```
</details>
# SPF
Aadd a new SPF DNS record of the `TXT` type for your domain that will be used with Postmoogle,
with format: `v=spf1 ip4:SERVER_IP4 -all` (replace `SERVER_IP4` with your server's IP address),
for servers with IPv6: `v=spf1 ip6:SERVER_IP6 -all` (you may use both `ip4` and `ip6` in one TXT record).
<details>
<summary>Example</summary>
```bash
$ dig txt example.com
; <<>> DiG 9.18.6 <<>> txt example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24796
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com. IN TXT
;; ANSWER SECTION:
example.com. 1799 IN TXT "v=spf1 ip4:111.111.111.111 -all"
;; Query time: 36 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 04 21:35:04 EEST 2022
;; MSG SIZE rcvd: 255
```
</details>
# DMARC
Add a new DMARC DNS record of the `TXT` type for subdomain `_dmarc` with a proper policy.
The simplest policy you can use is: `v=DMARC1; p=quarantine;`.
<details>
<summary>Example</summary>
```bash
$ dig txt _dmarc.example.com
; <<>> DiG 9.18.6 <<>> txt _dmarc.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57306
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;_dmarc.example.com. IN TXT
;; ANSWER SECTION:
_dmarc.example.com. 1799 IN TXT "v=DMARC1; p=quarantine;"
;; Query time: 46 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Sun Sep 04 21:31:30 EEST 2022
;; MSG SIZE rcvd: 79
```
</details>
# DKIM
Add new DKIM DNS record of `TXT` type for subdomain `postmoogle._domainkey` that will be used with postmoogle.
You can get that signature using the `!pm dkim` command:
<details>
<summary>!pm dkim</summary>
DKIM signature is: `v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE=`.
You need to add it to your DNS records (if not already):
Add new DNS record with type = `TXT`, key (subdomain/from): `postmoogle._domainkey` and value (to):
```
v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE=
```
Without that record other email servers may reject your emails as spam, kupo.
</details>
<details>
<summary>Example</summary>
```bash
$ dig TXT postmoogle._domainkey.example.com
; <<>> DiG 9.18.6 <<>> TXT postmoogle._domainkey.example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 59014
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;postmoogle._domainkey.example.com. IN TXT
;; ANSWER SECTION:
postmoogle._domainkey.example.com. 600 IN TXT "v=DKIM1; k=rsa; p=MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAxJVqmBHhK9FY93q1o3WEaP2GKMh/3LNMyvi1uSjOKxIyfWv685KxX1EUrbHakQRCTtUM7efKEsXsXBh+DQru2TE32yFpL9afA5BbHj3KePGFY8KJ2m0sQxbQcvn2KjJC0IQ15mk0rninPhtphU/2zLsd6e7Rl1m3L+9Osk320GbfDgSKjRPcSiwVMbLJpSOP0H0F3cIu+c1fHZHfmWy0O+us42C3HTLTlD779LTnQnKlAOQD/+DYYqz6TGGxEwUG2BRQ8O8w7/wXEkg/6a/MxNtPnc59g29CpqRsDkuYiR3UIpqzLDoqHlaoKNbYy34R+4aIjfNpmZyR5kIumws+3MJtJt9UhBTMloqd8lZDPaPmX2NEDqbcSTkHMQrphk+EWSCc7OvbKRaXZ0SyJLpLjxRwKrpeO0JAI0ZpnAFS11uBEe9GSS8uzIIFNYVD1vHloAFKvUJEhyuVyz9/SyqTnArN3ZTiC5cqD1MB86q5QPrKqZfp1dAnv7xAJThL0AP/AgMBAAE="
;; Query time: 90 msec
;; SERVER: 1.1.1.1#53(1.1.1.1) (UDP)
;; WHEN: Mon Sep 05 16:16:21 EEST 2022
;; MSG SIZE rcvd: 525
```
</details>
# rDNS
> additional PTR record will help you to get better spam score
Configure Reverse DNS of your server. Unfortunately, rDNS is provider-specific, so you have to find out how to configure it with your hosting provider. Search for something like: `PROVIDER configure "rdns"` (where `PROVIDER` is your hosting provider name)

View File

@@ -7,13 +7,21 @@ import (
"gitlab.com/etke.cc/go/logger" "gitlab.com/etke.cc/go/logger"
) )
var log *logger.Logger var (
log *logger.Logger
domains []string
)
// SetLogger for utils // SetLogger for utils
func SetLogger(loggerInstance *logger.Logger) { func SetLogger(loggerInstance *logger.Logger) {
log = loggerInstance log = loggerInstance
} }
// SetDomains for later use
func SetDomains(slice []string) {
domains = slice
}
// Mailbox returns mailbox part from email address // Mailbox returns mailbox part from email address
func Mailbox(email string) string { func Mailbox(email string) string {
index := strings.LastIndex(email, "@") index := strings.LastIndex(email, "@")
@@ -24,16 +32,24 @@ func Mailbox(email string) string {
} }
// EmailsList returns human-readable list of mailbox's emails for all available domains // EmailsList returns human-readable list of mailbox's emails for all available domains
func EmailsList(mailbox string, domains []string) string { func EmailsList(mailbox string, domain string) string {
var msg strings.Builder var msg strings.Builder
domain = SanitizeDomain(domain)
msg.WriteString(mailbox)
msg.WriteString("@")
msg.WriteString(domain)
count := len(domains) - 1 count := len(domains) - 1
for i, domain := range domains { for i, aliasDomain := range domains {
msg.WriteString(mailbox)
msg.WriteString("@")
msg.WriteString(domain)
if i < count { if i < count {
msg.WriteString(", ") msg.WriteString(", ")
} }
if aliasDomain == domain {
continue
}
msg.WriteString(mailbox)
msg.WriteString("@")
msg.WriteString(aliasDomain)
} }
return msg.String() return msg.String()
@@ -44,6 +60,22 @@ func Hostname(email string) string {
return email[strings.LastIndex(email, "@")+1:] return email[strings.LastIndex(email, "@")+1:]
} }
// SanitizeDomain checks that input domain is available for use
func SanitizeDomain(domain string) string {
domain = strings.TrimSpace(domain)
if domain == "" {
return domains[0]
}
for _, allowed := range domains {
if domain == allowed {
return domain
}
}
return domains[0]
}
// Bool converts string to boolean // Bool converts string to boolean
func Bool(str string) bool { func Bool(str string) bool {
str = strings.ToLower(str) str = strings.ToLower(str)