Compare commits
	
		
			No commits in common. "b4d5eebb60860171fcb0fa07d70ddce5d25c3fec" and "9783e3bb47b783bbb9946eb3c050c78d970d11a5" have entirely different histories.
		
	
	
		
			b4d5eebb60
			...
			9783e3bb47
		
	
		
@ -8,6 +8,7 @@ import (
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/smtp"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/ooni/probe-cli/v3/internal/engine/experiment/urlgetter"
 | 
			
		||||
@ -25,6 +26,9 @@ var (
 | 
			
		||||
 | 
			
		||||
	// errInvalidScheme indicates that the scheme is invalid
 | 
			
		||||
	errInvalidScheme = errors.New("scheme must be smtp(s)")
 | 
			
		||||
 | 
			
		||||
	// errInvalidPort indicates that the port provided could not be parsed as an int
 | 
			
		||||
	errInvalidPort = errors.New("Port number is not a valid integer")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
@ -33,16 +37,14 @@ const (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Config contains the experiment config.
 | 
			
		||||
type Config struct{}
 | 
			
		||||
 | 
			
		||||
type RuntimeConfig struct {
 | 
			
		||||
type Config struct {
 | 
			
		||||
	host       string
 | 
			
		||||
	port       string
 | 
			
		||||
	forced_tls bool
 | 
			
		||||
	noop_count uint8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func config(input model.MeasurementTarget) (*RuntimeConfig, error) {
 | 
			
		||||
func config(input model.MeasurementTarget) (*Config, error) {
 | 
			
		||||
	if input == "" {
 | 
			
		||||
		// TODO: static input data (eg. gmail/riseup..)
 | 
			
		||||
		return nil, errNoInputProvided
 | 
			
		||||
@ -66,11 +68,16 @@ func config(input model.MeasurementTarget) (*RuntimeConfig, error) {
 | 
			
		||||
			port = "465"
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		// Valid port is checked by URL parsing
 | 
			
		||||
		port = parsed.Port()
 | 
			
		||||
		// Check that requested port is a valid integer
 | 
			
		||||
		_, err := strconv.Atoi(parsed.Port())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errInvalidPort
 | 
			
		||||
		} else {
 | 
			
		||||
			port = parsed.Port()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	valid_config := RuntimeConfig{
 | 
			
		||||
	valid_config := Config{
 | 
			
		||||
		host:       parsed.Hostname(),
 | 
			
		||||
		forced_tls: parsed.Scheme == "smtps",
 | 
			
		||||
		port:       port,
 | 
			
		||||
@ -314,6 +321,7 @@ func (m Measurer) Run(
 | 
			
		||||
	ctx context.Context, sess model.ExperimentSession,
 | 
			
		||||
	measurement *model.Measurement, callbacks model.ExperimentCallbacks,
 | 
			
		||||
) error {
 | 
			
		||||
 | 
			
		||||
	log := sess.Logger()
 | 
			
		||||
	trace := measurexlite.NewTrace(0, measurement.MeasurementStartTimeSaved)
 | 
			
		||||
 | 
			
		||||
@ -364,7 +372,7 @@ func (m Measurer) Run(
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Try EHLO + NoOps
 | 
			
		||||
			if !tcp_session.smtp("localhost", config.noop_count) {
 | 
			
		||||
			if !tcp_session.smtp("localhost", 10) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
@ -378,7 +386,7 @@ func (m Measurer) Run(
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if !tcp_session.smtp("localhost", config.noop_count) {
 | 
			
		||||
			if !tcp_session.smtp("localhost", 10) {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -1,185 +0,0 @@
 | 
			
		||||
package smtp
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/ooni/probe-cli/v3/internal/engine/mockable"
 | 
			
		||||
	"github.com/ooni/probe-cli/v3/internal/model"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func plaintextListener() net.Listener {
 | 
			
		||||
	l, err := net.Listen("tcp", "127.0.0.1:0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
 | 
			
		||||
			panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return l
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func tlsListener(l net.Listener) net.Listener {
 | 
			
		||||
	return tls.NewListener(l, &tls.Config{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func listener_addr(l net.Listener) string {
 | 
			
		||||
	return l.Addr().String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ValidSMTPServer(conn net.Conn) {
 | 
			
		||||
	for {
 | 
			
		||||
		command, err := bufio.NewReader(conn).ReadString('\n')
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if command == "" {
 | 
			
		||||
		} else if command == "NOOP" {
 | 
			
		||||
			conn.Write([]byte("250 2.0.0 Ok\n"))
 | 
			
		||||
		} else if command == "STARTTLS" {
 | 
			
		||||
			conn.Write([]byte("220 2.0.0 Ready to start TLS\n"))
 | 
			
		||||
			// TODO: conn.Close does not actually close connection? or does client not detect it?
 | 
			
		||||
			conn.Close()
 | 
			
		||||
			return
 | 
			
		||||
		} else if strings.HasPrefix(command, "EHLO") {
 | 
			
		||||
			conn.Write([]byte("250 mock.example.com\n"))
 | 
			
		||||
		}
 | 
			
		||||
		conn.Write([]byte("\n"))
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TCPServer(l net.Listener) {
 | 
			
		||||
	for {
 | 
			
		||||
		conn, err := l.Accept()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
		conn.Write([]byte("220 mock.example.com ESMTP (spam is not appreciated)\n"))
 | 
			
		||||
		ValidSMTPServer(conn)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMeasurer_run(t *testing.T) {
 | 
			
		||||
	// runHelper is an helper function to run this set of tests.
 | 
			
		||||
	runHelper := func(input string) (*model.Measurement, model.ExperimentMeasurer, error) {
 | 
			
		||||
		m := NewExperimentMeasurer(Config{})
 | 
			
		||||
		if m.ExperimentName() != "smtp" {
 | 
			
		||||
			t.Fatal("invalid experiment name")
 | 
			
		||||
		}
 | 
			
		||||
		if m.ExperimentVersion() != "0.0.1" {
 | 
			
		||||
			t.Fatal("invalid experiment version")
 | 
			
		||||
		}
 | 
			
		||||
		ctx := context.Background()
 | 
			
		||||
		meas := &model.Measurement{
 | 
			
		||||
			Input: model.MeasurementTarget(input),
 | 
			
		||||
		}
 | 
			
		||||
		sess := &mockable.Session{
 | 
			
		||||
			MockableLogger: model.DiscardLogger,
 | 
			
		||||
		}
 | 
			
		||||
		callbacks := model.NewPrinterCallbacks(model.DiscardLogger)
 | 
			
		||||
		err := m.Run(ctx, sess, meas, callbacks)
 | 
			
		||||
		return meas, m, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("with empty input", func(t *testing.T) {
 | 
			
		||||
		_, _, err := runHelper("")
 | 
			
		||||
		if !errors.Is(err, errNoInputProvided) {
 | 
			
		||||
			t.Fatal("unexpected error", err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("with invalid URL", func(t *testing.T) {
 | 
			
		||||
		_, _, err := runHelper("\t")
 | 
			
		||||
		if !errors.Is(err, errInputIsNotAnURL) {
 | 
			
		||||
			t.Fatal("unexpected error", err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("with invalid scheme", func(t *testing.T) {
 | 
			
		||||
		_, _, err := runHelper("https://8.8.8.8:443/")
 | 
			
		||||
		if !errors.Is(err, errInvalidScheme) {
 | 
			
		||||
			t.Fatal("unexpected error", err)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("with broken TLS", func(t *testing.T) {
 | 
			
		||||
		p := plaintextListener()
 | 
			
		||||
		defer p.Close()
 | 
			
		||||
 | 
			
		||||
		l := tlsListener(p)
 | 
			
		||||
		defer l.Close()
 | 
			
		||||
		addr := listener_addr(l)
 | 
			
		||||
		go TCPServer(l)
 | 
			
		||||
 | 
			
		||||
		meas, m, err := runHelper("smtps://" + addr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tk := meas.TestKeys.(*TestKeys)
 | 
			
		||||
 | 
			
		||||
		for _, run := range tk.Runs {
 | 
			
		||||
			for _, handshake := range run.TLSHandshakes {
 | 
			
		||||
				if *handshake.Failure != "unknown_failure: remote error: tls: unrecognized name" {
 | 
			
		||||
					t.Fatal("expected unrecognized_name in TLS handshake")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if run.NoOpCounter != 0 {
 | 
			
		||||
				t.Fatalf("expected to not have any noops, not %d noops", run.NoOpCounter)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ask, err := m.GetSummaryKeys(meas)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal("cannot obtain summary")
 | 
			
		||||
		}
 | 
			
		||||
		summary := ask.(SummaryKeys)
 | 
			
		||||
		if summary.IsAnomaly {
 | 
			
		||||
			t.Fatal("expected no anomaly")
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("with broken starttls", func(t *testing.T) {
 | 
			
		||||
		l := plaintextListener()
 | 
			
		||||
		defer l.Close()
 | 
			
		||||
		addr := listener_addr(l)
 | 
			
		||||
 | 
			
		||||
		go TCPServer(l)
 | 
			
		||||
 | 
			
		||||
		meas, m, err := runHelper("smtp://" + addr)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tk := meas.TestKeys.(*TestKeys)
 | 
			
		||||
 | 
			
		||||
		for _, run := range tk.Runs {
 | 
			
		||||
			for _, handshake := range run.TLSHandshakes {
 | 
			
		||||
				if *handshake.Failure != "generic_timeout_error" {
 | 
			
		||||
					t.Fatal("expected timeout in TLS handshake")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if run.NoOpCounter != 0 {
 | 
			
		||||
				t.Fatalf("expected to not have any noops, not %d noops", run.NoOpCounter)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ask, err := m.GetSummaryKeys(meas)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatal("cannot obtain summary")
 | 
			
		||||
		}
 | 
			
		||||
		summary := ask.(SummaryKeys)
 | 
			
		||||
		if summary.IsAnomaly {
 | 
			
		||||
			t.Fatal("expected no anomaly")
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user