123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368openCoreopenAsyncopenAsync_smtpletlog=Lazy.forceAsync.Log.Global.log|>Smtp_mail_log.adjust_log_levels~remap_info_to:`Debug;;moduleConfig=structtypet={dir:string;host:string;port:int;tls:bool;send_n_messages:int;client_allowed_ciphers:[`Secure|`Openssl_default|`Onlyofstringlist];server_allowed_ciphers:[`Secure|`Openssl_default|`Onlyofstringlist];key_type:[`rsaofint|`ecdsaofstring|`dsaofint]}[@@derivingfields,sexp]letmake_tls_certificatest=letopensslargs=Async_shell.run"openssl"argsinlet%bind()=openssl["req";"-new";"-x509"(* generate the request and sign in one step *);"-newkey";"rsa:512"(* generate key *);"-nodes"(* don't encrypt the key *);"-batch"(* non interactive *);"-keyout";t.dir^/"ca.key";"-out";t.dir^/"ca.crt";"-days";"1"(* short shelf life is good for testing *);"-subj";"/CN=stress-test-CA/"]inlet%bind()=matcht.key_typewith|`rsabits->openssl["genrsa";"-out";t.dir^/"server.key";Int.to_stringbits]|`dsabits->openssl["dsaparam";"-genkey";"-out";t.dir^/"server.key";Int.to_stringbits]|`ecdsacurve->openssl["ecparam";"-name";curve;"-genkey";"-out";t.dir^/"server.key"]inlet%bind()=openssl["req";"-new";"-key";t.dir^/"server.key";"-nodes"(* don't encrypt the key *);"-batch"(* non interactive *);"-out";t.dir^/"server.csr";"-subj";sprintf"/CN=%s/"t.host]inlet%map()=openssl["x509";"-req";"-days";"1"(* short shelf life is good for testing *);"-CA";t.dir^/"ca.crt";"-CAkey";t.dir^/"ca.key";"-in";t.dir^/"server.csr";"-out";t.dir^/"server.crt";"-set_serial";"1"]inletserver={Smtp_server.Config.Tls_options.version=None;options=None;name=None;crt_file=t.dir^/"server.crt";key_file=t.dir^/"server.key";ca_file=Some(t.dir^/"ca.crt");ca_path=None;allowed_ciphers=t.server_allowed_ciphers}inletclient=[(Smtp_client.Config.Domain_suffix.of_stringt.host,{Smtp_client.Config.Tls.version=None;options=None;name=None;ca_file=Some(t.dir^/"ca.crt");ca_path=None;mode=`Required;certificate_mode=`Verify;allowed_ciphers=t.client_allowed_ciphers});(Smtp_client.Config.Domain_suffix.of_string"",{Smtp_client.Config.Tls.version=None;options=None;name=None;ca_file=None;ca_path=None;mode=`Required;certificate_mode=`Verify(* Causes the message to fail if we have to wrong host *);allowed_ciphers=t.client_allowed_ciphers})]inserver,client;;letserver_and_client_config~concurrent_receiverst=let%bindtls_options=ift.tlsthen(let%maptls_options=make_tls_certificatestinSometls_options)elsereturnNoneinletspool_dir=t.dir^/"spool-not-used"inlet%map()=Deferred.all_unit[Unix.mkdir~p:()spool_dir]inletclient={Smtp_client.Config.greeting=Some"stress-test";tls=Option.value_map~f:sndtls_options~default:[];send_receive_timeout=`This(Time.Span.of_sec2.);final_ok_timeout=`This(Time.Span.of_sec5.)}inletserver={Smtp_server.Config.where_to_listen=[Localhost_on_portt.port];max_concurrent_receive_jobs_per_port=concurrent_receivers;timeouts=Smtp_server.Config.Timeouts.default;rpc_port=0(* not used *);malformed_emails=`Reject;max_message_size=Byte_units.of_megabytes1.;tls_options=Option.map~f:fsttls_options;tcp_options=None}inserver,client;;endletcounter=ref0letfinished=Ivar.create()letthrottle=ref(Throttle.create~continue_on_error:true~max_concurrent_jobs:1)letsend~config~client_configenvelope=incrcounter;letport=Config.portconfiginlethost=Config.hostconfigindon't_wait_for(Throttle.enqueue!throttle(fun()->Deferred.Or_error.try_with_join(fun()->Smtp_client.Tcp.with_~log:(Lazy.forceLog.Global.log)(Host_and_port.create~host~port)~config:client_config~f:(funclient->Smtp_client.send_envelopeclient~logenvelope>>|?Smtp_client.Envelope_status.ok_or_error~allow_rejected_recipients:false>>|Or_error.join>>|?ignore)))>>|Result.iter_error~f:(Log.Global.error!"buh???: %{Error#hum}"));;letmain~dir~host~port~tls~send_n_messages~num_copies~concurrent_senders~concurrent_receivers~message_from_stdin~client_allowed_ciphers~server_allowed_ciphers~key_type()=letconfig={Config.dir;host;port;tls;send_n_messages;client_allowed_ciphers;server_allowed_ciphers;key_type}inlet%bindenvelopes=ifmessage_from_stdinthen(letstdin=Lazy.forceReader.stdininSmtp_server.read_bsmtpstdin|>Pipe.map~f:Or_error.ok_exn|>Pipe.to_list)else(letrecipients=[Email_address.of_string_exn"test@example.com"]inletemail=Email.Simple.create~from:(Email_address.local_address())~subject:"Stress test"~to_:recipients(Email.Simple.Content.text_utf8"Stress Test")inletsender=`Nullinreturn[Smtp_envelope.create~sender~recipients~email()])inletenvelopes=List.initnum_copies~f:(fun_->envelopes)|>List.concatinthrottle:=Throttle.create~continue_on_error:true~max_concurrent_jobs:concurrent_senders;let%bindserver_config,client_config=Config.server_and_client_config~concurrent_receiversconfiginletmoduleServer=Smtp_server.Make(structopenSmtp_monad.Let_syntaxmoduleState=Smtp_server.Plugin.Simple.StatemoduleSession=structincludeSmtp_server.Plugin.Simple.Sessionletextensions~state:__=[Smtp_server.Plugin.Extension.Start_tls(modulestructtypesession=tletupgrade_to_tls~log:_t=return{twithtls=true}end:Smtp_server.Plugin.Start_tlswithtypesession=t)];;endmoduleEnvelope=structincludeSmtp_server.Plugin.Simple.Envelopeletprocess~state:_~log:_~flows:__sessiontemail=letenvelope=smtp_envelopetemailinif!counter>=Config.send_n_messagesconfigthenIvar.fill_if_emptyfinished()elsesend~config~client_configenvelope;return(sprintf"stress-test:%d"!counter);;endletrpcs()=[]end)inlet%bindserver=Server.start~server_state:()~log~config:server_config>>|Or_error.ok_exninList.iterenvelopes~f:(send~config~client_config);let%bind()=Ivar.readfinishedin(* Wait for all pending messages to clear *)let%bind()=Throttle.prior_jobs_done!throttleinlet%bind()=Clock.after(sec0.1)inmatch%bindServer.closeserverwith|Errore->Error.raisee|Ok()->Deferred.return();;letcipher_list=Command.Arg_type.create(funs->`Only(String.split~on:':'s))letkey_type=Command.Arg_type.create(funs->matchString.split~on:':'swith|["rsa";bits]->`rsa(Int.of_stringbits)|["dsa";bits]->`dsa(Int.of_stringbits)|["ecdsa";curve]->`ecdsacurve|_->failwith"not a recognized key type. Supported rsa:BITS, dsa:BITS, ecdsa:CURVE");;letcommand=letopenCommand.Let_syntaxinCommand.async~summary:"Stress-test an smtp server by repeatedly sending and receiving a message read \
from stdin"[%map_openletdir=flag"-dir"(optionalstring)~doc:" Working dir"|>map~f:(function|Somedir->dir|None->Core.Unix.mkdtemp"/tmp/stress-test-")andhost=flag"-host"(optional_with_default"localhost"string)~doc:" Hostname to listen on"andport=flag"-port"(optional_with_default2525int)~doc:" Port to listen on"andtls=flag"-tls"no_arg~doc:" Run the stress test with TLS enabled"andsend_n_messages=flag"-send-n-messages"~aliases:["-n"](optional_with_default1000int)~doc:" Number of messages to send"andnum_copies=flag"-num-copies"(optional_with_default1int)~doc:" Number of copies of each (the) message to have in circulation"andconcurrent_senders=flag"-concurrent-senders"(optional_with_default1int)~doc:" Number of concurrent senders"andconcurrent_receivers=flag"-concurrent-receivers"(optional_with_default1int)~doc:" Number of concurrent receivers"andmessage_from_stdin=flag"-message-from-stdin"no_arg~doc:" Read the message from stdin, otherwise generate a simple message"and()=flag"-log-level"(optionalLog.Level.arg)~doc:" Log level"|>map~f:(Option.iter~f:Log.Global.set_level)andclient_allowed_ciphers=flag"-client-allowed-ciphers"(optional_with_default`Securecipher_list)~doc:" Restrict client side SSL ciphers"andserver_allowed_ciphers=flag"-server-allowed-ciphers"(optional_with_default`Securecipher_list)~doc:" Restrict server side SSL ciphers"andkey_type=flag"-key-type"(optional_with_default(`rsa2048)key_type)~doc:" TLS Key type to use/generate"infun()->main~dir~host~port~tls~send_n_messages~num_copies~concurrent_senders~concurrent_receivers~message_from_stdin~client_allowed_ciphers~server_allowed_ciphers~key_type()];;