author | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-09-22 23:16:07 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2016-10-09 23:51:04 UTC |
parent | 5f15b4e0409bacfba2d843bd25ad71f093432287 |
chasquid.go | +50 | -8 |
internal/envelope/envelope.go | +12 | -0 |
diff --git a/chasquid.go b/chasquid.go index d629438..f7205c3 100644 --- a/chasquid.go +++ b/chasquid.go @@ -376,9 +376,10 @@ type Conn struct { maxDataSize int64 // Connection information. - netconn net.Conn - tc *textproto.Conn - mode SocketMode + netconn net.Conn + tc *textproto.Conn + mode SocketMode + tlsConnState *tls.ConnectionState // System configuration. config *config.Config @@ -675,6 +676,8 @@ func (c *Conn) DATA(params string, tr *trace.Trace) (code int, msg string) { tr.LazyPrintf("-> ... %d bytes of data", len(c.data)) + c.addReceivedHeader() + // There are no partial failures here: we put it in the queue, and then if // individual deliveries fail, we report via email. msgID, err := c.queue.Put(c.mailFrom, c.rcptTo, c.data) @@ -700,6 +703,34 @@ func (c *Conn) DATA(params string, tr *trace.Trace) (code int, msg string) { return 250, msgs[rand.Int()%len(msgs)] } +func (c *Conn) addReceivedHeader() { + var v string + + if c.completedAuth { + v += fmt.Sprintf("from user %s@%s\n", c.authUser, c.authDomain) + } else { + v += fmt.Sprintf("from %s\n", c.netconn.RemoteAddr().String()) + } + + v += fmt.Sprintf("by %s (chasquid SMTP) over ", c.hostname) + if c.tlsConnState != nil { + v += fmt.Sprintf("TLS (%#x-%#x)\n", + c.tlsConnState.Version, c.tlsConnState.CipherSuite) + } else { + v += "plain text!\n" + } + + // Note we must NOT include c.rcptTo, that would leak BCCs. + v += fmt.Sprintf("(envelope from %q)\n", c.mailFrom) + + // This should be the last part in the Received header, by RFC. + // The ";" is a mandatory separator. The date format is not standard but + // this one seems to be widely used. + // https://tools.ietf.org/html/rfc5322#section-3.6.7 + v += fmt.Sprintf("on ; %s\n", time.Now().Format(time.RFC1123Z)) + c.data = envelope.AddHeader(c.data, "Received", v) +} + func (c *Conn) STARTTLS(params string, tr *trace.Trace) (code int, msg string) { if c.onTLS { return 503, "You are already wearing that!" @@ -712,23 +743,34 @@ func (c *Conn) STARTTLS(params string, tr *trace.Trace) (code int, msg string) { tr.LazyPrintf("<- 220 You experience a strange sense of peace") - client := tls.Server(c.netconn, c.tlsConfig) - err = client.Handshake() + server := tls.Server(c.netconn, c.tlsConfig) + err = server.Handshake() if err != nil { - return 554, fmt.Sprintf("error in client handshake: %v", err) + return 554, fmt.Sprintf("error in TLS handshake: %v", err) } tr.LazyPrintf("<> ... jump to TLS was successful") // Override the connections. We don't need the older ones anymore. - c.netconn = client - c.tc = textproto.NewConn(client) + c.netconn = server + c.tc = textproto.NewConn(server) + + // Take the connection state, so we can use it later for logging and + // tracing purposes. + cstate := server.ConnectionState() + c.tlsConnState = &cstate // Reset the envelope; clients must start over after switching to TLS. c.resetEnvelope() c.onTLS = true + // If the client requested a specific server and we complied, that's our + // identity from now on. + if name := c.tlsConnState.ServerName; name != "" { + c.hostname = name + } + // 0 indicates not to send back a reply. return 0, "" } diff --git a/internal/envelope/envelope.go b/internal/envelope/envelope.go index 4c31214..d0eb1d2 100644 --- a/internal/envelope/envelope.go +++ b/internal/envelope/envelope.go @@ -3,6 +3,7 @@ package envelope import ( + "fmt" "strings" "blitiri.com.ar/go/chasquid/internal/set" @@ -36,3 +37,14 @@ func DomainIn(addr string, locals *set.String) bool { return locals.Has(domain) } + +func AddHeader(data []byte, k, v string) []byte { + // If the value contains newlines, indent them properly. + if v[len(v)-1] == '\n' { + v = v[:len(v)-1] + } + v = strings.Replace(v, "\n", "\n\t", -1) + + header := []byte(fmt.Sprintf("%s: %s\n", k, v)) + return append(header, data...) +}