// Decide what to do and then do it if it is a rejection or a tempfail. // If given an id (and it is in the message handling phase) we call // RejectData(). This is our convenience driver for the rules engine, // Decide(). // // Returns false if the message was accepted, true if decider() handled // a rejection or tempfail. func decider(ph Phase, evt smtpd.EventInfo, c *Context, convo *smtpd.Conn, id string) bool { res := Decide(ph, evt, c) logDnsbls(c) // Terrible hack to log DNS lookup failure specifics. if c.domerr != nil && c.trans.log != nil { lmsg := fmt.Sprintf("! %s\n", c.domerr) if lmsg != c.trans.lastamsg { c.trans.log.Write([]byte(lmsg)) c.trans.lastamsg = lmsg } } // The moment a rule sets a savedir, it becomes sticky. // This lets you select a savedir based on eg from matching // instead of having to do games later. if sd := c.withprops["savedir"]; sd != "" { c.trans.savedir = sd } // rule notes are deliberately logged every time they hit. // this may be a mistake given EHLO retrying as HELO, but // I'll see. if note := c.withprops["note"]; note != "" { c.trans.log.Write([]byte(fmt.Sprintf("! rule note: %s\n", note))) } // Disable TLS if desired, or just disable asking for client certs. switch c.withprops["tls-opt"] { case "off": convo.Config.TLSConfig = nil case "no-client": // 'tls-opt no-client' without certificates should not // crash. if convo.Config.TLSConfig != nil { convo.Config.TLSConfig.ClientAuth = tls.NoClientCert } } if res == aNoresult || res == aAccept { return false } if ph == pConnect { // TODO: have some way to stall or reject connections // in smtpd. Or should that be handled outside of it? // Right now a reject result means 'drop', stall will // implicitly cause us to go on. return res == aReject } msg := c.withprops["message"] switch res { case aReject: // This is kind of a hack. // We assume that 'id' is only set when we should report it, // which is kind of safe. if msg != "" { if id != "" { msg += "\nRejected with ID " + id } convo.RejectMsg(msg) return true } // Default messages are kind of intricate. switch { case id != "" && ph == pMessage: convo.RejectMsg("We do not consent to you emailing %s\nRejected with ID %s", pluralRecips(c), id) case ph == pMessage || ph == pData: convo.RejectMsg("We do not consent to you emailing %s", pluralRecips(c)) case ph == pRto: convo.RejectMsg("We do not consent to you emailing that address") default: convo.Reject() } case aStall: if msg != "" { convo.TempfailMsg(msg) } else { convo.Tempfail() } default: panic("impossible res") } return true }