From fce6eb779b2576e2b7e14daf7300ed01ad172a2d Mon Sep 17 00:00:00 2001 From: ooninoob Date: Mon, 21 Nov 2022 19:25:23 +0100 Subject: [PATCH] Start work on IMAP probe --- internal/engine/experiment/imap/.smtp.go.swp | Bin 0 -> 24576 bytes .../engine/experiment/imap/.smtp_test.go.swp | Bin 0 -> 28672 bytes internal/engine/experiment/imap/imap.go | 416 ++++++++++++++++++ internal/engine/experiment/imap/imap_test.go | 186 ++++++++ 4 files changed, 602 insertions(+) create mode 100644 internal/engine/experiment/imap/.smtp.go.swp create mode 100644 internal/engine/experiment/imap/.smtp_test.go.swp create mode 100644 internal/engine/experiment/imap/imap.go create mode 100644 internal/engine/experiment/imap/imap_test.go diff --git a/internal/engine/experiment/imap/.smtp.go.swp b/internal/engine/experiment/imap/.smtp.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..c4f8bb682bb7263202a642c7b577a5ee662f82a3 GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/internal/engine/experiment/imap/.smtp_test.go.swp b/internal/engine/experiment/imap/.smtp_test.go.swp new file mode 100644 index 0000000000000000000000000000000000000000..5a742bedb807ab21ba6fcf0ea4c3a34e2c981d9b GIT binary patch literal 28672 zcmeI43veA*d4M-0DHSaR9NL7uIKITml`CCIj$<6z0USBOF1DmviUY(!v$}V6wP^3| zo!z~%ge`CaVFqX?FeMpi0|Ti`!j!ahS{|JVOlZSkT1Wz&feD3{OnB7LmJnxJXs1KJ z|LpTVBs(t{XLgg%d-uH0zq{x0|Nm-sc>nIbYHMYa!SQ0l*s^@6dd~k28rvrfqZL@b z1fT41%x35{PnOCs-YNwSP5NGasOfvN)?n4Khio^reAje_EVpjE){wQ-w0yf^x#3XI z2%Gw!na~QtO5Hno6-A0d0)+&6NuU+Z4Q_p*arvf^VTpc4X}vmU-F3ZE7GZ@13JDYv zC?rrwppZZzfkFa>1PTc}M1PTch5-224NT85FA%Q{y zPeB4^%`hJRO~Y7zA(^rNC;R^sFEWhB;W&H;z7Ai2&%izKr*Ip*8{P$zupV9x7sG%3 zmSOw@{4Kl5UV0AGa7Q711hu?v9@DlhJlr{Ilop1}h18Pu(61)I@io)fG@B_F5mf=b$ z!5MHIh0rnhC^Vo7vv4nb7Cs0^;7#yG*bmbn$BNOn(r@(jaqjC24HX|N=`RVGviwEM zA2%D;XdJIpso_-@D%O(OXgXG<>NQFOZFzRPfmLn!*3^RC+>e*Y-14ZJGo8T7l7&uC z_1rnTu12p?TvW#N)ghx#YH3(zP?^S=`k7HC!5qL!|&J53QxJa=jhSPvgeowZgJavSoN<$aQ7PB1tXF?|8bY4X*p51sW|hjRX+X!)7{BH2vK*Xs$|aH5 z=+IE--5b4P>nlsS%IlumZ#s5uYVY)9bo=QmMd;a)zGR*?>)(^W!Be+wASmOpt3sgT-!&Ovsnc6y=596c~G*i8D` zRc|awS?)1prKA2{26A)Ov7(V|RP{(ZKuyvFspL`_IwU>ABM-ah$~&W{4=u1He{O`E zHvPI4mbEzgr6}n~T$GHYxujAS9hS&sbiSId)_t$#)?|=~C#vLSPoCS%6Hzf!Nvq_R z$Q$3NT$6e!G7m{qxnD-i(r{(CGE&m_HJbg_DAZ-{>s6x6iE(iduF z+95RtqbeDcCK(p(0iznspL>G&jR z^&qLmtb&k7px&>_8>8EqlQGYceonfJ)uW#0>t~(TWM$idGF`=p#}uM#kxr@5lO!sf zCpxe~<;^J>o`VVl^Ux6YF9_Sa%`CoIqB=s!9!8U0{5m+59krFNkeXlauT1HF@gzuJ zWrA2O*=pe;mRxo{zs&SZoCTh0TPQ5c#A_}6NL#&nw>@&zNg(&xWj$vn_a) zi?-ml*5)dWC*c{hD|J^u+F8lIc5HeBqdQ=Vpd1y+PjbSk7SNThE1<1i)SM(p&Uz4D zz5l0}ti2~zSlJVcu0MVqWy#W3mMnUC!IN}lXFy$=TL`l<*N-_tSrXR0`I7TjQTfTF z;;=ko6wzZ_({$}>d9D#wrkXN0%#};BERfe#mgTm?YKhmG_lZs@-fdGaVMf)4MJ6!j zqyBg}rG+v($?7|4MY$AKn_Ea$x?yy5w5evopSQn z+dY~0N(^tMcpX~OZ*nObZG-rVF2%nPxKu;mJwJ)9`pdZB4{=W=DxT%y^s}uw+bbD% zL)MR~Y#2=@KeKKrnOqFn|3AS#d^>w~+5fW@*2gcn{uUgAqwo>93FhHixEfvsl5ZPa z3hUs0%K96)6PDlrydHMJ7I-QAf<64t;Zb-e+yd`_x4?eb2C~opKlbd8z!%{zco!5$ zA%Q{yg#-!-6cQ*TP)MMVKp}xb0;jJ8G6hCY#e1URV#Ap;DJ4p^Q;A(G=NfTqM2Qu^ z^%Sa$Rok>iRFWI5M3NmDq`$>t*4aH>o9i`9XF1Uq2CYWJ^p~+Y?1&;&RrCi{%^lxI1NMh+KDuqGi@22z_-qw-oSjGhNwa#{4{nBHXn{|ff)H-PN_F-hw~_WuvVgYaHh0t+PF7>JEu z^ady3M{o~(0^SD;FbXe*2ieDqP5&^A!IiKPE`buPhks>X|2g<;_y8=zG;D`U;6)%d z{6|qv+zUtHR=5S;4A;P8*zF&Lqwrz41O5bBFb4flh9C9OMsVO-*Z~*8e`B{l0sjbh z!iV7q%)(aK4Clg65YUg|9=I7Cuwfpy!UgacHv6x@F}NT89{vbI*bN(CJ&0}pLO71i z{(-6@JYBCZi2mV1zZU0 z;3aSZoBiYP5IhKX!y>!}HbEKWNZyA?)!6$=Les%3XEJqLjak%bsPf0XiDtmln&g)= zdOS4#6^R6NjDp+=_mFrDAd@bg&W+n}3tP~h&b1i=+Qm$wDBlEn|Wt?{; zmy14HtnZnowcW&BnC2seIFB#S)anieg=zlT^4KU$^EuC>!ZiORP4mg9Ea!30izc$n zKCdS+?W7=s;c04pZ@0Zi^YT@Szb4b$_&#r?bGXVD08Q~!h|F&?BgY;D{tQ!awTXsb9Da!d_uB9(*{n^HfHdiO^cn}r2bA9x2&tu z4|3y{*0PMGQ>!gE(rR~9s}}axRVtR&r27F3(>gJ33;0gN$4c(E1V!Ei9f8>tpJkN9 zEGc$!U)lledr%ZR=f;xOy&3T@>3YSd3WwDUUQV>3%NWz=5eUNpI zA%)5^Fj^F*#cQ9(&c16?cW$B7X5f`o6aUAHBUwLnEPwQhvhyj7g(9txqwAh^Jyur? z4|YCQ?T?KVlXl~pU3(^SE94br;omBYCQFg4 zJmQw<)VPu0`%mX=-}MzG>2mPm)xT9KEW#Vpv60O)Q*B z(c=>nllfxlalU4WGLq_2VI?VKeH{PJ-bS|Wk$~jU393ovmE=dqt?Rh2`ejR!w%_EA zl&d9LEnFN!j3iQ5lk;Lo!sDi=@2j@ri(iM_IDBqJ;{6r5h`pCCJK$ax^*wqw^poG| zZY#O;D=;S6MWnL-7vJrR**D7me?jl|u+g)>{~mlE{t&8Ag8SIh-v#f78{rZ-AH<%2 z0)7gQ!2=-v{72yr;2<<$ABbIF_WbhQ!8hR>@JaCDW$-xr`3K-`kiGt4n1Tryfpg$E zd-?CfG583`e*aByIa~zi!^_}!AA525D6GITv>=3?umS#uef>A!>+k@475*4(cnw?# z=fO+i9C(C1{(W#a+y(D}0QSKsTnT5vzq7ahC-^dmF5qMEXYf`SgYUAhzYX3FEeK&8 zp5R{m0KN_X44;Oha0Cv+jj$8MmtXGNxgdW0XLJ4*=TE`M;r(zJYH&3uI2#`2e*YcZ z0k^>n48kG!H~0eF3U7ed!4A+zr!p$pq_3`^eDXO&r5Wcs)y(Z0vaUv*yqGmotPvG! z_nvx+)pGU>&j;pz^dh>ifK}VOl@S3P}By59d|GC1ej8$u1Z4q|SJF1E zu8Z0fE$5ygtzNtI5~)2CYCHMfAydQ|N!!~saoIEQ^fYF9_snL0w)Ba156!NvLsKN( z!=q6*GyfILtnV%0jwy?{U&0qKg~F;(SVbR}$nzO}BfuhdviO#SvH4f^Ey>Bh0TEj} z19tR%pWHdwO++8t#QV@+)mI?VhW<&bj(Tl`vZ1=y_VsBigPwUkk2Evg5atv(G85%C zeW|)b$;S_=OTDGP!7-xwF`FfO^u1%duBQ;GLyjKFH?h-STfvbj6P~mZqsLxbH=$E? zCT&A!G(E9%Vl--`PN8a^6>IIPVT(`KmibVW?@D!&NNf0Ng)T`ai$5WiMPAJciB)#^ zYC}p+j$XazK{<_8Dzg8-ojv!(?8{~Ue?h#}`#F34pTYeg-}=7?-V8SUKFIg}7s6Ta zWA^`#z<1&E@Hx00Zh-UPo0Kiz_kRXH4ey05PzI@Y1cqS<&Vn=HKH5|qg#-!-6cQ*T zP)MMVKp}xb0)+$$2|TAIkZiQF{i%esU-b-WPttzXGlK;7C|Cz~{nK7v`;{a)P0;gscFhmS;JUzQWFBN~=UyF+M``WET P{vN<;N`7eyYQFymc 0 { + s.runner.logger.Infof("Trying to generate no-op traffic") + s.tk.NoOpCounter = 0 + for s.tk.NoOpCounter < noop { + s.tk.NoOpCounter += 1 + s.runner.logger.Infof("NoOp Iteration %d", s.tk.NoOpCounter) + + conn.Write([]byte("A1 NOOP\n")) + command, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + s.error(err) + break + } + if !strings.Contains(command, "OK NOOP") { + s.error(errors.New("Unexpected IMAP reply: " + command)) + break + } + } + + if s.tk.NoOpCounter == noop { + s.runner.logger.Infof("Successfully generated no-op traffic") + return true + } else { + s.runner.logger.Infof("Failed no-op traffic at iteration %d", s.tk.NoOpCounter) + return false + } + } + + return true +} + +// Run implements ExperimentMeasurer.Run +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) + + config, err := config(measurement.Input) + if err != nil { + // Invalid input data, we don't even generate report + return err + } + + tk := new(TestKeys) + measurement.TestKeys = tk + + ctx, cancel := context.WithTimeout(ctx, 60*time.Second) + defer cancel() + + tlsconfig := tls.Config{ + InsecureSkipVerify: false, + ServerName: config.host, + } + + runner := &TCPRunner{ + trace: trace, + logger: log, + ctx: ctx, + tk: tk, + tlsconfig: &tlsconfig, + host: config.host, + port: config.port, + } + + // First resolve DNS + addrs, success := runner.resolve(config.host) + if !success { + return nil + } + + for _, addr := range addrs { + tcp_session, success := runner.conn(addr, config.port) + if !success { + continue + } + defer tcp_session.Close() + + if config.forced_tls { + // Direct TLS connection + if !tcp_session.handshake() { + continue + } + + // Try EHLO + NoOps + if !tcp_session.imap(config.noop_count) { + continue + } + } else { + // StartTLS... + if !tcp_session.starttls("A1 STARTTLS\n") { + continue + } + + if !tcp_session.imap(config.noop_count) { + continue + } + } + } + + return nil +} + +// NewExperimentMeasurer creates a new ExperimentMeasurer. +func NewExperimentMeasurer(config Config) model.ExperimentMeasurer { + return Measurer{Config: config} +} + +// SummaryKeys contains summary keys for this experiment. +// +// Note that this structure is part of the ABI contract with ooniprobe +// therefore we should be careful when changing it. +type SummaryKeys struct { + //DNSBlocking bool `json:"facebook_dns_blocking"` + //TCPBlocking bool `json:"facebook_tcp_blocking"` + IsAnomaly bool `json:"-"` +} + +// GetSummaryKeys implements model.ExperimentMeasurer.GetSummaryKeys. +func (m Measurer) GetSummaryKeys(measurement *model.Measurement) (interface{}, error) { + sk := SummaryKeys{IsAnomaly: false} + _, ok := measurement.TestKeys.(*TestKeys) + if !ok { + return sk, errors.New("invalid test keys type") + } + return sk, nil +} diff --git a/internal/engine/experiment/imap/imap_test.go b/internal/engine/experiment/imap/imap_test.go new file mode 100644 index 0000000..1fbfc15 --- /dev/null +++ b/internal/engine/experiment/imap/imap_test.go @@ -0,0 +1,186 @@ +package imap + +import ( + "bufio" + "context" + "crypto/tls" + //"encoding/json" + "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 ValidIMAPServer(conn net.Conn) { + for { + command, err := bufio.NewReader(conn).ReadString('\n') + if err != nil { + return + } + + if strings.Contains(command, "NOOP") { + conn.Write([]byte("A1 OK NOOP completed.\n")) + } else if command == "STARTTLS" { + conn.Write([]byte("A1 OK Begin TLS negotiation now.\n")) + // TODO: conn.Close does not actually close connection? or does client not detect it? + conn.Close() + return + } + conn.Write([]byte("\n")) + } +} + +func TCPServer(l net.Listener) { + for { + conn, err := l.Accept() + if err != nil { + continue + } + defer conn.Close() + conn.Write([]byte("* OK [CAPABILITY IMAP4rev1 SASL-IR LOGIN-REFERRALS ID ENABLE IDLE LITERAL+ STARTTLS LOGINDISABLED] howdy, ready.\n")) + ValidIMAPServer(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() != "imap" { + 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("imaps://" + 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("imap://" + addr) + if err != nil { + t.Fatal(err) + } + + tk := meas.TestKeys.(*TestKeys) + //bs, _ := json.Marshal(tk) + //fmt.Println(string(bs)) + + for _, run := range tk.Runs { + for _, handshake := range run.TLSHandshakes { + if *handshake.Failure != "unknown_failure: tls: first record does not look like a TLS handshake" { + + t.Fatal("expected broken 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") + } + }) +}