diff --git a/server.go b/server.go index eacdba3..4185957 100644 --- a/server.go +++ b/server.go @@ -17,6 +17,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" tracesdk "go.opentelemetry.io/otel/sdk/trace" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" "go.opentelemetry.io/otel/trace" ) @@ -139,7 +140,8 @@ type Server struct { EnableXCLIENT bool // EnableProxyProtocol enables Proxy command support (disabled by default, since it is security risk) EnableProxyProtocol bool - + // HideTransactionHeader hides transaction header + HideTransactionHeader bool // TLSConfig is used both for STARTTLS and operation over TLS channel TLSConfig *tls.Config // ForceTLS requires connections to be encrypted @@ -186,9 +188,8 @@ func (srv *Server) startTransaction(c net.Conn) (t *Transaction) { remoteAddr := c.RemoteAddr().(*net.TCPAddr) ctxWithTracer, span := srv.Tracer.Start(ctx, "transaction", trace.WithSpanKind(trace.SpanKindServer), // важно - trace.WithAttributes(attribute.String("remote_addr", c.RemoteAddr().String())), - trace.WithAttributes(attribute.String("remote_ip", remoteAddr.IP.String())), - trace.WithAttributes(attribute.Int("remote_port", remoteAddr.Port)), + trace.WithAttributes(semconv.ClientSocketAddress(remoteAddr.IP.String())), + trace.WithAttributes(semconv.ClientSocketPort(remoteAddr.Port)), ) t = &Transaction{ ID: span.SpanContext().TraceID().String(), diff --git a/server_counters_test.go b/server_counters_test.go index 91b00da..bd73850 100644 --- a/server_counters_test.go +++ b/server_counters_test.go @@ -75,7 +75,7 @@ func TestCounters(t *testing.T) { if srv.lastTransactionStartedAt.IsZero() { t.Errorf("transaction time is not set") } - if time.Now().Sub(srv.lastTransactionStartedAt) > 3*time.Second { + if time.Since(srv.lastTransactionStartedAt) > 3*time.Second { t.Errorf("last transaction time is too old") } } diff --git a/transaction_auth.go b/transaction_auth.go index 1b6492a..b286f89 100644 --- a/transaction_auth.go +++ b/transaction_auth.go @@ -6,6 +6,7 @@ import ( "strings" "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func (t *Transaction) handleAUTH(cmd command) { @@ -58,7 +59,7 @@ func (t *Transaction) handleAUTH(cmd command) { } username = string(parts[1]) password = string(parts[2]) - break + case "LOGIN": encodedUsername := "" if len(cmd.fields) < 3 { @@ -87,7 +88,7 @@ func (t *Transaction) handleAUTH(cmd command) { } username = string(byteUsername) password = string(bytePassword) - break + default: t.LogDebug("unknown authentication mechanism: %s", mechanism) t.reply(502, "Unknown authentication mechanism") @@ -106,8 +107,7 @@ func (t *Transaction) handleAUTH(cmd command) { // i know saving password here is security risk, but we can implement something like // Haraka plugin to prevent credential leaks // https://haraka.github.io/plugins/prevent_credential_leaks - t.Span.SetAttributes(attribute.String("username", username)) - t.Span.SetAttributes(attribute.String("password", mask(password))) + t.Span.SetAttributes(semconv.EnduserID(username)) + t.Span.SetAttributes(attribute.String("enduser.password", mask(password))) t.reply(235, "OK, you are now authenticated") - return } diff --git a/transaction_data.go b/transaction_data.go index c418f7f..58a94c1 100644 --- a/transaction_data.go +++ b/transaction_data.go @@ -31,7 +31,7 @@ var uniqueHeaders = []string{ "Subject", } -func (t *Transaction) handleDATA(cmd command) { +func (t *Transaction) handleDATA(_ command) { var checkErr error var deliverErr error var createdAt time.Time @@ -69,15 +69,21 @@ func (t *Transaction) handleDATA(cmd command) { } t.LogDebug("DATA is called...") t.reply(354, "Ok, you managed to talk me into accepting your message. Go on, end your data with .") - t.conn.SetDeadline(time.Now().Add(t.server.DataTimeout)) + err := t.conn.SetDeadline(time.Now().Add(t.server.DataTimeout)) + if err != nil { + t.LogError(err, "while setting deadline for connection") + return + } data := bytes.NewBufferString("") reader := textproto.NewReader(t.reader).DotReader() - _, err := io.CopyN(data, reader, int64(t.server.MaxMessageSize)) + _, err = io.CopyN(data, reader, int64(t.server.MaxMessageSize)) if err != nil { if err == io.EOF { // EOF was reached before MaxMessageSize, so we can accept and deliver message t.Body = data.Bytes() - t.AddHeader("MSMTPD-Transaction-Id", t.ID) + if !t.server.HideTransactionHeader { + t.AddHeader("MSMTPD-Transaction-Id", t.ID) + } t.AddReceivedLine() // will be added as first one t.LogDebug("Parsing message body with size %v...", data.Len()) t.Span.SetAttributes(attribute.Int("size", data.Len())) @@ -205,5 +211,4 @@ func (t *Transaction) handleDATA(cmd command) { )) t.Hate(tooBigMessagePenalty) t.reset() - return } diff --git a/transaction_handler.go b/transaction_handler.go index 1635316..1767365 100644 --- a/transaction_handler.go +++ b/transaction_handler.go @@ -12,40 +12,28 @@ func (t *Transaction) handle(line string) { switch cmd.action { case "PROXY": t.handlePROXY(cmd) - break case "HELO": t.handleHELO(cmd) - break case "EHLO": t.handleEHLO(cmd) - break case "MAIL": t.handleMAIL(cmd) - break case "RCPT": t.handleRCPT(cmd) - break case "STARTTLS": t.handleSTARTTLS(cmd) - break case "DATA": t.handleDATA(cmd) - break case "RSET": t.handleRSET(cmd) - break case "NOOP": t.handleNOOP(cmd) - break case "QUIT": t.handleQUIT(cmd) - break case "AUTH": t.handleAUTH(cmd) - break case "XCLIENT": t.handleXCLIENT(cmd) - break default: t.Hate(unknownCommandPenalty) t.LogDebug("Unsupported command received: %s", line) @@ -53,19 +41,16 @@ func (t *Transaction) handle(line string) { } } -func (t *Transaction) handleRSET(cmd command) { +func (t *Transaction) handleRSET(_ command) { t.reset() t.reply(250, "I forgot everything you have said, go ahead please!") - return } -func (t *Transaction) handleNOOP(cmd command) { +func (t *Transaction) handleNOOP(_ command) { t.reply(250, "I'm finishing procrastinating, go ahead please!") - return } -func (t *Transaction) handleQUIT(cmd command) { +func (t *Transaction) handleQUIT(_ command) { t.reply(221, fmt.Sprintf("Farewell, my friend! Transaction %s is finished", t.ID)) t.close() - return } diff --git a/transaction_helo.go b/transaction_helo.go index a3f0d49..dfe6bc0 100644 --- a/transaction_helo.go +++ b/transaction_helo.go @@ -4,6 +4,7 @@ import ( "fmt" "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.21.0" ) func (t *Transaction) handleHELO(cmd command) { @@ -21,7 +22,7 @@ func (t *Transaction) handleHELO(cmd command) { t.HeloName = cmd.fields[1] t.Protocol = SMTP t.Span.SetAttributes(attribute.String("helo", t.HeloName)) - t.Span.SetAttributes(attribute.String("protocol", "SMTP")) + t.Span.SetAttributes(semconv.NetProtocolName("smtp")) for k := range t.server.HeloCheckers { err = t.server.HeloCheckers[k](t) if err != nil { @@ -68,7 +69,7 @@ func (t *Transaction) handleEHLO(cmd command) { t.HeloName = cmd.fields[1] t.Protocol = ESMTP t.Span.SetAttributes(attribute.String("ehlo", t.HeloName)) - t.Span.SetAttributes(attribute.String("protocol", "ESMTP")) + t.Span.SetAttributes(semconv.NetProtocolName("esmtp")) for k := range t.server.HeloCheckers { err = t.server.HeloCheckers[k](t) if err != nil { diff --git a/transaction_mailfrom.go b/transaction_mailfrom.go index 28770e9..6643520 100644 --- a/transaction_mailfrom.go +++ b/transaction_mailfrom.go @@ -66,5 +66,4 @@ func (t *Transaction) handleMAIL(cmd command) { ) t.reply(250, "Ok, it makes sense, go ahead please!") t.Love(commandExecutedProperly) - return } diff --git a/transaction_proxy.go b/transaction_proxy.go index a54593f..de56b37 100644 --- a/transaction_proxy.go +++ b/transaction_proxy.go @@ -22,8 +22,8 @@ func (t *Transaction) handlePROXY(cmd command) { return } var ( - newAddr net.IP = nil - newTCPPort uint64 = 0 + newAddr net.IP + newTCPPort uint64 err error ) switch cmd.fields[1] { diff --git a/transaction_rcpt_to.go b/transaction_rcpt_to.go index cb2ebae..9d85ee9 100644 --- a/transaction_rcpt_to.go +++ b/transaction_rcpt_to.go @@ -62,13 +62,10 @@ func (t *Transaction) handleRCPT(cmd command) { switch len(t.RcptTo) { case 1: t.LogInfo("Recipient %s will be 1st one in transaction!", addr) - break case 2: t.LogInfo("Recipient %s will be 2nd one in transaction!", addr) - break case 3: t.LogInfo("Recipient %s will be 3rd one in transaction!", addr) - break default: t.LogInfo("Recipient %s will be %dth one in transaction!", addr, len(t.RcptTo)) } @@ -86,5 +83,4 @@ func (t *Transaction) handleRCPT(cmd command) { if len(t.RcptTo) == 1 { // too many recipients should not give too many love for transaction t.Love(commandExecutedProperly) } - return } diff --git a/transaction_startTLS.go b/transaction_startTLS.go index 9a8500b..fa6c4c5 100644 --- a/transaction_startTLS.go +++ b/transaction_startTLS.go @@ -9,6 +9,7 @@ import ( ) func (t *Transaction) handleSTARTTLS(cmd command) { + var err error if t.Encrypted { t.LogDebug("Connection is already encrypted!") t.reply(502, "Already running in TLS") @@ -21,7 +22,8 @@ func (t *Transaction) handleSTARTTLS(cmd command) { t.LogDebug("STARTTLS [%s] is received...", cmd.line) tlsConn := tls.Server(t.conn, t.server.TLSConfig) t.reply(220, "Connection is encrypted, we can talk freely now!") - if err := tlsConn.Handshake(); err != nil { + err = tlsConn.Handshake() + if err != nil { t.LogError(err, "couldn't perform handshake") t.reply(550, "TLS Handshake error") return @@ -32,7 +34,13 @@ func (t *Transaction) handleSTARTTLS(cmd command) { t.reset() // Reset deadlines on the underlying connection before I replace it // with a TLS connection - t.conn.SetDeadline(time.Time{}) + err = t.conn.SetDeadline(time.Time{}) + if err != nil { + t.LogError(err, "error setting deadline for encrypted connection") + t.reply(550, "TLS Handshake error") + return + } + // Replace connection with a TLS connection t.conn = tlsConn t.reader = bufio.NewReader(tlsConn) diff --git a/transaction_test.go b/transaction_test.go index 5895f5e..e2c536f 100644 --- a/transaction_test.go +++ b/transaction_test.go @@ -61,7 +61,7 @@ func TestKarma(t *testing.T) { if err != nil { t.Errorf("Data failed: %v", err) } - _, err = fmt.Fprintf(wc, internal.MakeTestMessage("sender@example.org", "recipient@example.net")) + _, err = fmt.Fprint(wc, internal.MakeTestMessage("sender@example.org", "recipient@example.net")) if err != nil { t.Errorf("Data body failed: %v", err) } diff --git a/transaction_xclient.go b/transaction_xclient.go index 73c28fd..acd5b0d 100644 --- a/transaction_xclient.go +++ b/transaction_xclient.go @@ -54,9 +54,9 @@ func (t *Transaction) handleXCLIENT(cmd command) { newUsername = value continue case "PROTO": - if value == "SMTP" { + if value == string(SMTP) { newProto = SMTP - } else if value == "ESMTP" { + } else if value == string(ESMTP) { newProto = ESMTP } continue