func (l *TLSLog) initConfig(config *tls.Config) { l.log = "" l.logRand = newLogRand(config.Rand) config.Rand = l.logRand config.SessionTicketsDisabled = false if config.ClientSessionCache == nil { config.ClientSessionCache = tls.NewLRUClientSessionCache(1) } }
// standaloneTLSTicketKeyRotation governs over the array of TLS ticket keys used to de/crypt TLS tickets. // It periodically sets a new ticket key as the first one, used to encrypt (and decrypt), // pushing any old ticket keys to the back, where they are considered for decryption only. // // Lack of entropy for the very first ticket key results in the feature being disabled (as does Go), // later lack of entropy temporarily disables ticket key rotation. // Old ticket keys are still phased out, though. // // Stops the ticker when returning. func standaloneTLSTicketKeyRotation(c *tls.Config, ticker *time.Ticker, exitChan chan struct{}) { defer ticker.Stop() // The entire page should be marked as sticky, but Go cannot do that // without resorting to syscall#Mlock. And, we don't have madvise (for NODUMP), too. ☹ keys := make([][32]byte, 1, NumTickets) rng := c.Rand if rng == nil { rng = rand.Reader } if _, err := io.ReadFull(rng, keys[0][:]); err != nil { c.SessionTicketsDisabled = true // bail if we don't have the entropy for the first one return } c.SessionTicketKey = keys[0] // SetSessionTicketKeys doesn't set a 'tls.keysAlreadySet' c.SetSessionTicketKeys(setSessionTicketKeysTestHook(keys)) for { select { case _, isOpen := <-exitChan: if !isOpen { return } case <-ticker.C: rng = c.Rand // could've changed since the start if rng == nil { rng = rand.Reader } var newTicketKey [32]byte _, err := io.ReadFull(rng, newTicketKey[:]) if len(keys) < NumTickets { keys = append(keys, keys[0]) // manipulates the internal length } for idx := len(keys) - 1; idx >= 1; idx-- { keys[idx] = keys[idx-1] // yes, this makes copies } if err == nil { keys[0] = newTicketKey } // pushes the last key out, doesn't matter that we don't have a new one c.SetSessionTicketKeys(setSessionTicketKeysTestHook(keys)) } } }
// setupTLSConfig returns a tls.Config for a credential set func setupTLSConfig(cert []byte, key []byte, ca []byte) (*tls.Config, error) { // TLS config var tlsConfig tls.Config //Use only modern ciphers tlsConfig.CipherSuites = []uint16{ tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, } // Use only TLS v1.2 tlsConfig.MinVersion = tls.VersionTLS12 // Don't allow session resumption tlsConfig.SessionTicketsDisabled = true certPool := x509.NewCertPool() certPool.AppendCertsFromPEM(ca) tlsConfig.RootCAs = certPool tlsConfig.ClientCAs = certPool tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert keypair, err := tls.X509KeyPair(cert, key) if err != nil { return &tlsConfig, err } tlsConfig.Certificates = []tls.Certificate{keypair} return &tlsConfig, nil }
// Process a single connection. func process(cid int, nc net.Conn, certs []tls.Certificate, logf io.Writer, smtplog io.Writer, baserules []*Rule) { var evt smtpd.EventInfo var convo *smtpd.Conn var logger *smtpLogger var l2 io.Writer var gotsomewhere, stall, sesscounts bool var cfg smtpd.Config defer nc.Close() trans := &smtpTransaction{} trans.savedir = savedir trans.raddr = nc.RemoteAddr() trans.laddr = nc.LocalAddr() prefix := fmt.Sprintf("%d/%d", os.Getpid(), cid) trans.rip, _, _ = net.SplitHostPort(trans.raddr.String()) trans.lip, _, _ = net.SplitHostPort(trans.laddr.String()) var c *Context // nit: in the presence of yakkers, we must know whether or not // the rules are good because bad rules turn *everyone* into // yakkers (since they prevent clients from successfully EHLO'ing). rules, rulesgood := setupRules(baserules) // A yakker is a client that is repeatedly connecting to us // without doing anything successfully. After a certain number // of attempts we turn them off. We only do this if we're logging // SMTP commands; if we're not logging, we don't care. // This is kind of a hack, but this code is for Chris and this is // what Chris cares about. // sesscounts is true if this session should count for being a // 'bad' session if we don't get far enough. Sessions with TLS // errors don't count, as do sessions with bad rules or sessions // where yakCount == 0. sesscounts = rulesgood && yakCount > 0 hit, cnt := yakkers.Lookup(trans.rip, yakTimeout) if yakCount > 0 && hit && cnt >= yakCount && smtplog != nil { // nit: if the rules are bad and we're stalling anyways, // yakkers still have their SMTP transactions not logged. c = newContext(trans, stallall) stall = true sesscounts = false } else { c = newContext(trans, rules) } //fmt.Printf("rules are:\n%+v\n", c.ruleset) if smtplog != nil && !stall { logger = &smtpLogger{} logger.prefix = []byte(prefix) logger.writer = bufio.NewWriterSize(smtplog, 8*1024) trans.log = logger l2 = logger } sname := trans.laddr.String() if srvname != "" { sname = srvname } else { lip, _, _ := net.SplitHostPort(sname) // we don't do a verified lookup of the local IP address // because it's theoretically under your control, so if // you want to forge stuff that's up to you. nlst, err := net.LookupAddr(lip) if err == nil && len(nlst) > 0 { sname = nlst[0] if sname[len(sname)-1] == '.' { sname = sname[:len(sname)-1] } } } if connfile != "" { dm, err := loadConnFile(connfile) if err != nil { warnf("error loading per-connection rules '%s': %s\n", connfile, err) } // dm.find() explicitly works even on nil dm, so we don't // need to guard it. if pd := dm.find(nc); pd != nil { if pd.myname != "" { sname = pd.myname } certs = pd.certs } } cfg.LocalName = sname cfg.SayTime = true cfg.SftName = "sinksmtp" cfg.Announce = "This server does not deliver email." // stalled conversations are always slow, even if -S is not set. // TODO: make them even slower than this? I probably don't care. if goslow || stall { cfg.Delay = time.Second / 10 } // Don't offer TLS to hosts that have too many TLS failures. // We give hosts *two* tries at setting up TLS because some // hosts start by offering SSLv2, which is an instant-fail, // even if they support stuff that we do. We hope that their // SSLv2 failure will cause them to try again in another // connection with TLS only. // See https://code.google.com/p/go/issues/detail?id=3930 blocktls, blcount := notls.Lookup(trans.rip, tlsTimeout) if len(certs) > 0 && !(blocktls && blcount >= 2) { var tlsc tls.Config tlsc.Certificates = certs // if there is already one TLS failure for this host, // it might be because of a bad client certificate. // so on the second time around we don't ask for one. // (More precisely we only ask for a client cert if // there are no failures so far.) // Another reason for failure here is a SSLv3 only // host without a client certificate. This produces // the error: // tls: received unexpected handshake message of type *tls.clientKeyExchangeMsg when waiting for *tls.certificateMsg //if blcount == 0 { // tlsc.ClientAuth = tls.VerifyClientCertIfGiven //} // Now generally disabled since I discovered it causes // SSLv3 handshakes to always fail. TODO: better fix with // config-file control or something. tlsc.SessionTicketsDisabled = true tlsc.ServerName = sname tlsc.BuildNameToCertificate() cfg.TLSConfig = &tlsc } // With everything set up we can now create the connection. convo = smtpd.NewConn(nc, cfg, l2) // Yes, we do rDNS lookup before our initial greeting banner and // thus can pause a bit here. Clients will cope, or at least we // don't care if impatient ones don't. trans.rdns, _ = LookupAddrVerified(trans.rip) // Check for an immediate result on the initial connection. This // may disable TLS or refuse things immediately. if decider(pConnect, evt, c, convo, "") { // TODO: somehow write a message and maybe log it. // this probably needs smtpd.go cooperation. // Right now we just close abruptly. if !stall { writeLog(logger, "! %s dropped on connect due to rule at %s\n", trans.rip, time.Now().Format(smtpd.TimeFmt)) } return } // Main transaction loop. We gather up email messages as they come // in, possibly failing various operations as we're told to. for { evt = convo.Next() switch evt.What { case smtpd.COMMAND: switch evt.Cmd { case smtpd.EHLO, smtpd.HELO: if decider(pHelo, evt, c, convo, "") { continue } trans.heloname = evt.Arg trans.from = "" trans.data = "" trans.hash = "" trans.bodyhash = "" trans.rcptto = []string{} if minphase == "helo" { gotsomewhere = true } case smtpd.MAILFROM: if decider(pMfrom, evt, c, convo, "") { continue } if trans.from != "" && !gotsomewhere && sesscounts { // We've been RSET, which potentially // counts as a failure for do-nothing // client detection. Note that we are // implicitly adding the *last* failed // attempt, the one that was RSET from. cnt = yakkers.Add(trans.rip, yakTimeout) // We're slightly generous with RSETs. // This has no net effect unless this // final attempt succeeds. if cnt > yakCount { writeLog(logger, "! %s added as a yakker at hit %d due to RSET\n", trans.rip, cnt) convo.TempfailMsg("Too many unsuccessful delivery attempts") // this will implicitly close // the connection. return } } trans.from = evt.Arg trans.data = "" trans.rcptto = []string{} if minphase == "from" { gotsomewhere = true } doAccept(convo, c, "") case smtpd.RCPTTO: if decider(pRto, evt, c, convo, "") { continue } trans.rcptto = append(trans.rcptto, evt.Arg) if minphase == "to" { gotsomewhere = true } doAccept(convo, c, "") case smtpd.DATA: if decider(pData, evt, c, convo, "") { continue } if minphase == "data" { gotsomewhere = true } doAccept(convo, c, "") } case smtpd.GOTDATA: // -minphase=message means 'message // successfully transmitted to us' as opposed // to 'message accepted'. if minphase == "message" { gotsomewhere = true } // message rejection is deferred until after logging // et al. trans.data = evt.Arg trans.when = time.Now() trans.tlson = convo.TLSOn trans.cipher = convo.TLSState.CipherSuite trans.servername = convo.TLSState.ServerName trans.tlsversion = convo.TLSState.Version trans.hash, trans.bodyhash = getHashes(trans) transid, err := handleMessage(prefix, trans, logf) // errors when handling a message always force // a tempfail regardless of how we're // configured. switch { case err != nil: convo.Tempfail() gotsomewhere = true case decider(pMessage, evt, c, convo, transid): // do nothing, already handled default: if minphase == "accepted" { gotsomewhere = true } doAccept(convo, c, transid) } case smtpd.TLSERROR: // any TLS error means we'll avoid offering TLS // to this source IP for a while. notls.Add(trans.rip, tlsTimeout) sesscounts = false } if evt.What == smtpd.DONE || evt.What == smtpd.ABORT { break } } // if the client did not issue any successful meaningful commands, // remember this. we squelch people who yak too long. // Once people are yakkers we don't count their continued failure // to do anything against them. // And we have to have good rules to start with because duh. switch { case !gotsomewhere && sesscounts: cnt = yakkers.Add(trans.rip, yakTimeout) // See if this transaction has pushed the client over the // edge to becoming a yakker. If so, report it to the SMTP // log. if cnt >= yakCount { writeLog(logger, "! %s added as a yakker at hit %d\n", trans.rip, cnt) } case yakCount > 0 && gotsomewhere: yakkers.Del(trans.rip) } }