1/*2Maddy Mail Server - Composable all-in-one email server.3Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors45This program is free software: you can redistribute it and/or modify6it under the terms of the GNU General Public License as published by7the Free Software Foundation, either version 3 of the License, or8(at your option) any later version.910This program is distributed in the hope that it will be useful,11but WITHOUT ANY WARRANTY; without even the implied warranty of12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the13GNU General Public License for more details.1415You should have received a copy of the GNU General Public License16along with this program. If not, see <https://www.gnu.org/licenses/>.17*/1819package dns2021import (22 "strings"2324 "github.com/foxcpp/maddy/framework/address"25 modconfig "github.com/foxcpp/maddy/framework/config/module"26 "github.com/foxcpp/maddy/framework/dns"27 "github.com/foxcpp/maddy/framework/exterrors"28 "github.com/foxcpp/maddy/framework/module"29 "github.com/foxcpp/maddy/internal/check"30)3132func requireMatchingRDNS(ctx check.StatelessCheckContext) module.CheckResult {33 if ctx.MsgMeta.Conn == nil {34 ctx.Logger.Msg("locally-generated message, skipping")35 return module.CheckResult{}36 }37 if ctx.MsgMeta.Conn.RDNSName == nil {38 ctx.Logger.Msg("rDNS lookup is disabled, skipping")39 return module.CheckResult{}40 }4142 rdnsNameI, err := ctx.MsgMeta.Conn.RDNSName.Get()43 if err != nil {44 reason, misc := exterrors.UnwrapDNSErr(err)45 return module.CheckResult{46 Reason: &exterrors.SMTPError{47 Code: exterrors.SMTPCode(err, 450, 550),48 EnhancedCode: exterrors.SMTPEnchCode(err, exterrors.EnhancedCode{0, 7, 25}),49 Message: "DNS error during policy check",50 CheckName: "require_matching_rdns",51 Err: err,52 Reason: reason,53 Misc: misc,54 },55 }56 }57 if rdnsNameI == nil {58 return module.CheckResult{59 Reason: &exterrors.SMTPError{60 Code: 550,61 EnhancedCode: exterrors.EnhancedCode{5, 7, 25},62 Message: "No PTR record found",63 CheckName: "require_matching_rdns",64 Err: err,65 },66 }67 }68 rdnsName := rdnsNameI.(string)6970 srcDomain := strings.TrimSuffix(ctx.MsgMeta.Conn.Hostname, ".")71 rdnsName = strings.TrimSuffix(rdnsName, ".")7273 if dns.Equal(rdnsName, srcDomain) {74 ctx.Logger.Debugf("PTR record %s matches source domain, OK", rdnsName)75 return module.CheckResult{}76 }7778 return module.CheckResult{79 Reason: &exterrors.SMTPError{80 Code: 550,81 EnhancedCode: exterrors.EnhancedCode{5, 7, 25},82 Message: "rDNS name does not match source hostname",83 CheckName: "require_matching_rdns",84 },85 }86}8788func requireMXRecord(ctx check.StatelessCheckContext, mailFrom string) module.CheckResult {89 if mailFrom == "" {90 // Permit null reverse-path for bounces.91 return module.CheckResult{}92 }9394 _, domain, err := address.Split(mailFrom)95 if err != nil {96 return module.CheckResult{97 Reason: &exterrors.SMTPError{98 Code: 501,99 EnhancedCode: exterrors.EnhancedCode{5, 1, 8},100 Message: "Malformed sender address",101 CheckName: "require_mx_record",102 },103 }104 }105 if domain == "" {106 return module.CheckResult{107 Reason: &exterrors.SMTPError{108 Code: 501,109 EnhancedCode: exterrors.EnhancedCode{5, 1, 8},110 Message: "No domain part in address",111 CheckName: "require_mx_record",112 },113 }114 }115116 srcMx, err := ctx.Resolver.LookupMX(ctx, domain)117 if err != nil {118 reason, misc := exterrors.UnwrapDNSErr(err)119 return module.CheckResult{120 Reason: &exterrors.SMTPError{121 Code: exterrors.SMTPCode(err, 450, 550),122 EnhancedCode: exterrors.SMTPEnchCode(err, exterrors.EnhancedCode{0, 7, 0}),123 Message: "DNS error during policy check",124 CheckName: "require_mx_record",125 Err: err,126 Reason: reason,127 Misc: misc,128 },129 }130 }131132 if len(srcMx) == 0 {133 return module.CheckResult{134 Reason: &exterrors.SMTPError{135 Code: 501,136 EnhancedCode: exterrors.EnhancedCode{5, 7, 27},137 Message: "Domain in MAIL FROM does not have any MX records",138 CheckName: "require_mx_record",139 },140 }141 }142143 for _, mx := range srcMx {144 if mx.Host == "." {145 return module.CheckResult{146 Reason: &exterrors.SMTPError{147 Code: 501,148 EnhancedCode: exterrors.EnhancedCode{5, 7, 27},149 Message: "Domain in MAIL FROM has null MX record",150 CheckName: "require_mx_record",151 },152 }153 }154 }155156 return module.CheckResult{}157}158func init() {159 check.RegisterStatelessCheck("require_matching_rdns", modconfig.FailAction{Quarantine: true},160 requireMatchingRDNS, nil, nil, nil)161 check.RegisterStatelessCheck("require_mx_record", modconfig.FailAction{Quarantine: true},162 nil, requireMXRecord, nil, nil)163}