Exemple #1
0
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)
	}
}
Exemple #2
0
// 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))
		}
	}
}
Exemple #3
0
// 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
}
Exemple #4
0
// 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)
	}
}