package alarmdelivery import ( "bufio" "context" "fmt" "net" "strings" "sync" "testing" "time" "github.com/gosnmp/gosnmp" ) type memorySecrets map[string][]byte func (s memorySecrets) Get(key string) ([]byte, bool) { value, ok := s[key] return append([]byte(nil), value...), ok } func TestEmailTransportDeliversMessageToSMTPServer(t *testing.T) { clientConnection, serverConnection := net.Pipe() defer serverConnection.Close() var ( mu sync.Mutex message string ) serverDone := make(chan error, 1) go func() { reader := bufio.NewReader(serverConnection) writer := bufio.NewWriter(serverConnection) writeLine := func(value string) error { if _, err := fmt.Fprintf(writer, "%s\r\n", value); err != nil { return err } return writer.Flush() } if err := writeLine("220 smtp.test ESMTP ready"); err != nil { serverDone <- err return } var data strings.Builder inData := false for { line, err := reader.ReadString('\n') if err != nil { serverDone <- err return } trimmed := strings.TrimRight(line, "\r\n") if inData { if trimmed == "." { mu.Lock() message = data.String() mu.Unlock() inData = false if err := writeLine("250 queued"); err != nil { serverDone <- err return } continue } data.WriteString(line) continue } command := strings.ToUpper(strings.Fields(trimmed)[0]) switch command { case "EHLO", "HELO", "MAIL", "RCPT": if err := writeLine("250 ok"); err != nil { serverDone <- err return } case "DATA": inData = true if err := writeLine("354 end with dot"); err != nil { serverDone <- err return } case "QUIT": serverDone <- writeLine("221 bye") return default: serverDone <- fmt.Errorf("unexpected SMTP command %q", trimmed) return } } }() request := validRequest(ChannelEmail) request.Message = "serial channel alarm" request.Email = &EmailTarget{ Host: "smtp.test", Port: 25, From: "tera@test.local", To: []string{"operator@test.local"}, CC: []string{"audit@test.local"}, Subject: "TERA alarm\r\nInjected: no", } transport := EmailTransport{DialContext: func(context.Context, string, string) (net.Conn, error) { return clientConnection, nil }} if err := transport.Send(context.Background(), Attempt{Channel: ChannelEmail, Request: request}); err != nil { t.Fatal(err) } if err := <-serverDone; err != nil { t.Fatal(err) } mu.Lock() payload := message mu.Unlock() if !strings.Contains(payload, "To: operator@test.local") || !strings.Contains(payload, "Cc: audit@test.local") || !strings.Contains(payload, "serial channel alarm") { t.Fatalf("SMTP message = %q", payload) } if strings.Contains(payload, "\r\nInjected:") || !strings.Contains(payload, "Subject: TERA alarm Injected: no") { t.Fatalf("SMTP subject was not sanitized: %q", payload) } } func TestSNMPTrapTransportSendsV2CAlarmPDU(t *testing.T) { listener, err := net.ListenPacket("udp", "127.0.0.1:0") if err != nil { t.Fatal(err) } defer listener.Close() address := listener.LocalAddr().(*net.UDPAddr) request := validRequest(ChannelSNMPTrap) request.Message = "CH12 link down" request.SNMPTraps = []SNMPTrapTarget{{ TargetID: "noc", Host: "127.0.0.1", Port: address.Port, Version: "v2c", CredentialKey: "trap/noc/community", }} transport := SNMPTrapTransport{Secrets: memorySecrets{"trap/noc/community": []byte("private-test-community")}} if err := transport.Send(context.Background(), Attempt{Channel: ChannelSNMPTrap, Request: request}); err != nil { t.Fatal(err) } if err := listener.SetReadDeadline(time.Now().Add(time.Second)); err != nil { t.Fatal(err) } buffer := make([]byte, 65535) count, _, err := listener.ReadFrom(buffer) if err != nil { t.Fatal(err) } packet, err := (&gosnmp.GoSNMP{}).SnmpDecodePacket(buffer[:count]) if err != nil { t.Fatal(err) } if packet.Version != gosnmp.Version2c || packet.Community != "private-test-community" || packet.PDUType != gosnmp.SNMPv2Trap { t.Fatalf("SNMP packet = %+v", packet) } var foundMessage bool for _, variable := range packet.Variables { if variable.Name == ".1.3.6.1.4.1.55555.1.2" && string(variable.Value.([]byte)) == "CH12 link down" { foundMessage = true } } if !foundMessage { t.Fatalf("alarm message PDU not found: %+v", packet.Variables) } }