123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256moduleClient=structletsrc=Logs.Src.create"starttls"~doc:"logs starttls's events"lethxd_config=Hxd.O.defaultmoduleLog=(valLogs.src_logsrc:Logs.LOG)typet={q:q;fiber:fiber}and'astate=|Initialization:Tls.Engine.state->handshakestate|Send_handshake:Tls.Engine.state->handshakestate|Wait_handshake:Tls.Engine.state->waitstate|Send:Tls.Engine.state->sendstate|Wait:Tls.Engine.state->waitstate|Close:Tls.Engine.state->closestate|Send_failure:Tls.Engine.failure*Tls.Engine.state->sendstateandq=V:'a*'astate->qandhandshake=Cstruct.tandsend=Cstruct.t(* Cstruct_cap? *)andwait=unitandclose=unitandfiber=Fiber:('s,'error)Colombe.State.process->fibertypeColombe.Rfc1869.error+=|Unexpected_arguments|Unexpected_application_data|Unexpected_payload|Unexpected_SMTP_responseof{code:int;txts:stringlist}|End_of_streamtypeerror=Colombe.Rfc1869.errorletpp_errorppf=function|Unexpected_arguments->Fmt.stringppf"Unexpected_arguments"|Unexpected_application_data->Fmt.stringppf"Unexpected_application_data"|Unexpected_payload->Fmt.stringppf"Unexpected_payload"|Unexpected_SMTP_response{code;txts;}->Fmt.pfppf"(Unexpected_SMTP_response (@[<1>code: %d,@ txts= @[<hov>%a@]@]))"codeFmt.(Dump.liststring)txts|End_of_stream->Fmt.stringppf"End_of_stream"|err->Colombe.Rfc1869.pp_errorppferrletehlotargs=ifargs<>""thenErrorUnexpected_argumentselseOktletencodet=matcht.qwith|V(_,Initialization_)->Log.app(funm->m"Send STARTTLS");Colombe.Rfc1869.Request{verb="STARTTLS";args=[]}|V(handshake,Send_handshake_)->Log.app(funm->m"Send TLS handshake");letbuf=Cstruct.to_byteshandshakeinColombe.Rfc1869.Payload{buf;off=0;len=Bytes.lengthbuf}|V(send,Send_)->Log.app(funm->m"Send application data");letbuf=Cstruct.to_bytessendinColombe.Rfc1869.Payload{buf;off=0;len=Bytes.lengthbuf}|V(send,Send_failure(failure,_))->Log.err(funm->m"Send TLS failure (%s)"(Tls.Engine.string_of_failurefailure));letbuf=Cstruct.to_bytessendinColombe.Rfc1869.Payload{buf;off=0;len=Bytes.lengthbuf}|V(_,Wait_handshake_)->assertfalse|V(_,Wait_)->assertfalse|V(_,Close_)->assertfalselethandlet=matcht.qwith|V(_,Wait_handshakestate)->assert(Tls.Engine.handshake_in_progressstate=false);assert(Tls.Engine.can_handle_appdatastate);(* XXX(dinosaure): should not be assertions but [if]. *)letFiberfiber=t.fiberin(matchfiberwith|Colombe.State.Read_|Return_|Error_->failwith"Inner process of STARTTLS flow MUST start with a Write operation"|Colombe.State.Write{buffer;off;len;k=_;}->Log.app(funm->m"Fiber start with: @[<hov>%a@]"(Hxd_string.pphxd_config)(String.subbufferofflen));matchTls.Engine.send_application_datastate[Cstruct.of_stringbuffer~off~len]with|Some(state,send)->{twithq=V(send,Sendstate)}|None->t(* XXX(dinosaure): [None] is an error? *))|V(_,Send_handshakestate)->(* XXX(dinosaure): hmmhmm, if we look into [`q5] of [Sendmail_tls], we
possible reach end of handshake even if we just sended TLS-data. In
this case, [handle] should update internal state as [Wait_handshake]
does. It's an undefined behavior. *){twithq=V((),Wait_handshakestate)}|V(_,Sendstate)->letFiberfiber=t.fiberinletfiber=matchfiberwith|Colombe.State.Write{len;k;_}->Log.app(funm->m"%d byte(s) consumed on fiber"len);klen(* XXX(dinosaure): this is on top of this assumption:
[ocaml-tls] consumes entirely the fiber. *)|_->fiberinletq=matchfiberwith|Colombe.State.Read_->Log.app(funm->m"Fiber wants to read");V((),Waitstate)|Write{buffer;off;len;k=_}->Log.app(funm->m"Fiber wants to write: @[<hov>%a@]"(Hxd_string.pphxd_config)(String.subbufferofflen));(matchTls.Engine.send_application_datastate[Cstruct.of_stringbuffer~off~len]with|Some(state,send)->V(send,Sendstate)|None->V((),Waitstate)(* TODO! *))|Return_|Error_->Log.info(funm->m"Got Return or Error state from fiber.\n%!");(* XXX(dinosaure): any [Return] or [Error] wants to notify the server
to close the connection. *)V((),Closestate)in{fiber=Fiberfiber;q}|_->tletactiont=matcht.qwith|V(_,Initialization_)->Some(Colombe.Rfc1869.Recv_code220)|V(send,Send_handshake_)->letbuf=Cstruct.to_bytessendinSomeColombe.Rfc1869.(Send(Payload{buf;off=0;len=Bytes.lengthbuf;}))|V(send,Send_)->letbuf=Cstruct.to_bytessendinSomeColombe.Rfc1869.(Send(Payload{buf;off=0;len=Bytes.lengthbuf;}))|V(send,Send_failure_)->letbuf=Cstruct.to_bytessendinSomeColombe.Rfc1869.(Send(Payload{buf;off=0;len=Bytes.lengthbuf;}))|V(_,Wait_handshake_)->SomeColombe.Rfc1869.Waiting_payload|V(_,Wait_)->SomeColombe.Rfc1869.Waiting_payload|V(_,Close_)->Nonelethandle_handshaket~buf~off~lenstate=matchTls.Engine.handle_tlsstate(Cstruct.of_bytesbuf~off~len)with|`Ok(_,_,`Data(Some_))->ErrorUnexpected_application_data|`Ok(`Okstate,`ResponseNone,_)->ifTls.Engine.can_handle_appdatastate&&Tls.Engine.handshake_in_progressstate=falsethenOk(handle{twithq=V((),Wait_handshakestate)})(* here, a dragoon ... *)elseOk{twithq=V((),Wait_handshakestate)}|`Ok(`Okstate,`Response(Somesend),_)->Ok{twithq=V(send,Send_handshakestate)}|`Ok(`Eof,_,_)->ErrorEnd_of_stream|`Ok(`Alertalert,_,_)->Log.err(funm->m"Retrieve an alert: %s"(Tls.Packet.alert_type_to_stringalert));letstate,send=Tls.Engine.send_close_notifystateinOk{twithq=V(send,Sendstate)}(* XXX(dinosaure): check this branch! *)|`Fail(failure,`Responsesend)->Ok{twithq=V(send,Send_failure(failure,state))}[@@@warning"-27"]lethandle_tlst~buf~off~lenstate=matchTls.Engine.handle_tlsstate(Cstruct.of_bytesbuf~off~len)with|`Ok(`Okstate,`ResponseNone,`Data(Somedata))->Log.app(funm->m"Receive from the server: @[<hov>%a@]"(Hxd_string.pphxd_config)(Cstruct.to_stringdata));letFiberfiber=t.fiberinletrecgodata=function|Colombe.State.Read{buffer;off;len;k;}->letlen=minlen(Cstruct.lendata)inCstruct.blit_to_bytesdata0bufferofflen;go(Cstruct.shiftdatalen)(klen)|Write{buffer;off;len;k=_;}asfiber->Log.app(funm->m"Fiber wants to write: @[<hov>%a@]"(Hxd_string.pphxd_config)(String.subbufferofflen));(matchTls.Engine.send_application_datastate[Cstruct.of_string~off~lenbuffer]with|Some(state,send)->Ok{fiber=Fiberfiber;q=V(send,Sendstate);}|None->assertfalse)|Return_asfiber->Log.app(funm->m"Notify to close the process");letstate,send=Tls.Engine.send_close_notifystateinOk{fiber=Fiberfiber;q=V(send,Sendstate)}|Error_asfiber->(* XXX(dinosaure): [fiber] should take care to [QUIT] properly.
[STARTTLS] should not introspect [fiber] first, then [QUIT] it
outside the scope of the already negociated TLS flow. *)Log.err(funm->m"Fiber returns an error, notify to close the process");letstate,send=Tls.Engine.send_close_notifystateinOk{fiber=Fiberfiber;q=V(send,Sendstate)}ingodatafiber|`Ok(`Okstate,`Response(Somesend),`DataNone)->Ok{twithq=V(send,Sendstate)}|`Ok(`Okstate,`Response(Somesend),`Data(Somedata))->letFiberfiber=t.fiberinletrecgodata=function|Colombe.State.Read{buffer;off;len;k;}->letlen=minlen(Cstruct.lendata)inCstruct.blit_to_bytesdata0bufferofflen;go(Cstruct.shiftdatalen)(klen)|(Write_|Return_|Error_)asfiber->Ok{fiber=Fiberfiber;q=V(send,Sendstate)}ingodatafiber|`Ok(`Okstate,`ResponseNone,`DataNone)->Ok{twithq=V((),Waitstate)}|`Ok(`Eof,_,_)->ErrorEnd_of_stream|`Ok(`Alertalert,_,_)->Log.err(funm->m"Retrieve an alert: %s"(Tls.Packet.alert_type_to_stringalert));letstate,send=Tls.Engine.send_close_notifystateinOk{twithq=V(send,Sendstate)}(* XXX(dinosaure): check this branch! *)|`Fail(failure,`Responsesend)->Ok{twithq=V(send,Send_failure(failure,state))}letdecoderespt=matchresp,t.qwith|Colombe.Rfc1869.Response{code=220;_},V(handshake,Initializationstate)->Ok{twithq=V(handshake,Send_handshakestate)}|Payload{buf;off;len;},V(_,Send_handshakestate)->Log.app(funm->m"Receive TLS handshake (client sended handshake)");handle_handshaket~buf~off~lenstate|Payload{buf;off;len;},V(_,Wait_handshakestate)->Log.app(funm->m"Receive TLS handshake (client expected handshake)");handle_handshaket~buf~off~lenstate|Payload{buf;off;len;},V(_,Sendstate)->handle_tlst~buf~off~lenstate|Payload{buf;off;len;},V(_,Waitstate)->handle_tlst~buf~off~lenstate|Response_,V(handshake,Initializationstate)->assertfalse(* server sended an other SMTP code *)|Response{code;txts;},_->Error(Unexpected_SMTP_response{code;txts;})|Payload_,V(_,Initialization_)->ErrorUnexpected_payload|Payload_,V(_,Close_)->ErrorUnexpected_payload|Payload{buf;off;len;},V(_,Send_failure(_,state))->Ok{twithq=V((),Closestate)}(* XXX(dinosaure): need to check! *)letmail_from_t_mail_from=[]letrcpt_to_t_rcpt_to=[]endtypestate=Client.ttypefiber=Client.fiberletdescription:Colombe.Rfc1869.description={name="STARTTLS";elho="STARTTLS";verb=["STARTTLS"]}letextension=Colombe.Rfc1869.inj(moduleClient)moduleExtension=(valextension)letinjv=Extension.Tvletfiberfiber=Client.Fiberfiberletmakefiber?domainconfig=letconfig=matchdomainwith|None->config|Somedomain->Tls.Config.peerconfig(Domain_name.to_stringdomain)inletstate,handshake=Tls.Engine.clientconfigin{Client.q=V(handshake,Initializationstate);fiber}