123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448(*
* Copyright (c) 2012-2014 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2014 Hannes Mehnert <hannes@mehnert.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)openLwt.InfixopenSexplib.Convletdebug=reffalseletdebug_print=refPrintf.eprintflet()=tryignore(Sys.getenv"CONDUIT_DEBUG");debug:=truewithNot_found->()typetls_lib=OpenSSL|Native|No_tls[@@derivingsexp]letdefault_tls_library=(* TODO build time selection *)letdefault=ifConduit_lwt_tls.availablethenNativeelseifConduit_lwt_unix_ssl.availablethenOpenSSLelseNo_tlsinmatchString.lowercase_ascii(Sys.getenv"CONDUIT_TLS")with|"native"->Native|"openssl"|"libressl"->OpenSSL|"none"|"notls"->No_tls|_->default|exceptionNot_found->defaultlettls_library=refdefault_tls_librarylet()=if!debugthen!debug_print"Selected TLS library: %s\n"(Sexplib.Sexp.to_string(sexp_of_tls_lib!tls_library))type+'aio='aLwt.ttypeic=Lwt_io.input_channeltypeoc=Lwt_io.output_channeltypeclient_tls_config=[`Hostnameofstring]*[`IPofIpaddr_sexp.t]*[`Portofint][@@derivingsexp]typeclient=[`TLSofclient_tls_config|`TLS_nativeofclient_tls_config|`OpenSSLofclient_tls_config|`TCPof[`IPofIpaddr_sexp.t]*[`Portofint]|`Unix_domain_socketof[`Fileofstring]|`Vchan_directof[`Domidofint]*[`Portofstring]|`Vchan_domain_socketof[`Domain_nameofstring]*[`Portofstring]][@@derivingsexp]typeserver_tls_config=[`Crt_file_pathofstring]*[`Key_file_pathofstring]*[`Passwordofbool->string|`No_password]*[`Portofint][@@derivingsexp](** Configuration fragment for a listening TLS server *)typetcp_config=[`Portofint|`Socketof(Lwt_unix.file_descr[@sexp.opaque])][@@derivingsexp](** Set of ways to create TCP servers *)typeserver=[`TLSofserver_tls_config|`OpenSSLofserver_tls_config|`TLS_nativeofserver_tls_config|`TCPoftcp_config|`Unix_domain_socketof[`Fileofstring]|`Vchan_directofint*string|`Vchan_domain_socketofstring*string|`Launchdofstring][@@derivingsexp](** Set of supported listening mechanisms that are supported by this module. *)typetls_own_key=[`None|`TLSof[`Crt_file_pathofstring]*[`Key_file_pathofstring]*[`Passwordofbool->string|`No_password]][@@derivingsexp]typetls_server_key=tls_own_key[@@derivingsexp]typectx={src:Unix.sockaddroption;tls_own_key:tls_own_key;tls_authenticator:Conduit_lwt_tls.X509.authenticator;ssl_client_verify:Conduit_lwt_unix_ssl.Client.verify;ssl_ctx:Conduit_lwt_unix_ssl.Client.context;}letstring_of_unix_sockaddrsa=letopenUnixinmatchsawith|ADDR_UNIXs->Printf.sprintf"ADDR_UNIX(%s)"s|ADDR_INET(ia,port)->Printf.sprintf"ADDR_INET(%s,%d)"(string_of_inet_addria)portletsexp_of_ctxctx=[%sexp_of:stringoption*tls_own_key]((matchctx.srcwith|None->None|Somesa->Some(string_of_unix_sockaddrsa)),ctx.tls_own_key)typetcp_flow={fd:(Lwt_unix.file_descr[@sexp.opaque]);ip:Ipaddr_sexp.t;port:int;}[@@derivingsexp]typedomain_flow={fd:(Lwt_unix.file_descr[@sexp.opaque]);path:string}[@@derivingsexp]typevchan_flow={domid:int;port:string}[@@derivingsexp]typeflow=|TCPoftcp_flow|Domain_socketofdomain_flow|Vchanofvchan_flow[@@derivingsexp]letflow_of_fdfdsa=matchsawith|Unix.ADDR_UNIXpath->Domain_socket{fd;path}|Unix.ADDR_INET(ip,port)->TCP{fd;ip=Ipaddr_unix.of_inet_addrip;port}letdefault_ctx=lazy{src=None;tls_own_key=`None;tls_authenticator=Lazy.forceConduit_lwt_tls.X509.default_authenticator;ssl_client_verify=Conduit_lwt_unix_ssl.Client.default_verify;ssl_ctx=Conduit_lwt_unix_ssl.Client.default_ctx;}letinit?src?(tls_own_key=`None)?(tls_authenticator=Lazy.forceConduit_lwt_tls.X509.default_authenticator)?(ssl_ctx=Conduit_lwt_unix_ssl.Client.default_ctx)?(ssl_client_verify=Conduit_lwt_unix_ssl.Client.default_verify)()=letno_source_ctx={src=None;tls_own_key;tls_authenticator;ssl_ctx;ssl_client_verify}inmatchsrcwith|None->Lwt.returnno_source_ctx|Somehost->(letopenUnixinLwt_unix.getaddrinfohost"0"[AI_PASSIVE;AI_SOCKTYPESOCK_STREAM]>>=function|{ai_addr;_}::_->Lwt.return{no_source_ctxwithsrc=Someai_addr}|[]->Lwt.fail_with"Invalid conduit source address specified")moduleSockaddr_io=structletshutdown_no_exnfdmode=tryLwt_unix.shutdownfdmodewithUnix.Unix_error(Unix.ENOTCONN,_,_)->()letmake_fd_state()=ref`Openletmakefd=letfd_state=make_fd_state()inletclose_in()=match!fd_statewith|`Open->fd_state:=`In_closed;shutdown_no_exnfdUnix.SHUTDOWN_RECEIVE;Lwt.return_unit|`Out_closed->fd_state:=`Closed;Lwt_unix.closefd|`In_closed(* repeating on a closed channel is a noop in Lwt_io *)|`Closed->Lwt.return_unitinletclose_out()=match!fd_statewith|`Open->fd_state:=`Out_closed;shutdown_no_exnfdUnix.SHUTDOWN_SEND;Lwt.return_unit|`In_closed->fd_state:=`Closed;Lwt_unix.closefd|`Out_closed(* repeating on a closed channel is a noop in Lwt_io *)|`Closed->Lwt.return_unitinletic=Lwt_io.of_fd~close:close_in~mode:Lwt_io.inputfdinletoc=Lwt_io.of_fd~close:close_out~mode:Lwt_io.outputfdin(ic,oc)end(* Vanilla sockaddr connection *)moduleSockaddr_client=structletconnect?srcsa=Conduit_lwt_server.with_socketsa(funfd->(matchsrcwith|None->Lwt.return_unit|Somesrc_sa->Lwt_unix.bindfdsrc_sa)>>=fun()->Lwt_unix.connectfdsa>>=fun()->letic,oc=Sockaddr_io.makefdinLwt.return(fd,ic,oc))endmoduleSockaddr_server=structletset_sockopts_no_exnfd=tryLwt_unix.setsockoptfdLwt_unix.TCP_NODELAYtruewith(* This is expected for Unix domain sockets *)|Unix.Unix_error(Unix.EOPNOTSUPP,_,_)->()letprocess_accept?timeoutcallback(client,peeraddr)=set_sockopts_no_exnclient;letic,oc=Sockaddr_io.makeclientinletc=callback(flow_of_fdclientpeeraddr)icocinletevents=matchtimeoutwith|None->[c]|Somet->[c;Lwt_unix.sleep(float_of_intt)]inLwt.finalize(fun()->Lwt.pickevents)(fun()->Conduit_lwt_server.close(ic,oc))letinit~on?stop?backlog?timeoutcallback=(matchonwith|`Sockets->Lwt.returns|`Sockaddrsockaddr->Conduit_lwt_server.listen?backlogsockaddr)>>=Conduit_lwt_server.init?stop(process_accept?timeoutcallback)endletset_max_activemaxactive=Conduit_lwt_server.set_max_activemaxactive(** TLS client connection functions *)letconnect_with_tls_native~ctx(`Hostnamehostname,`IPip,`Portport)=letsa=Unix.ADDR_INET(Ipaddr_unix.to_inet_addrip,port)in(matchctx.tls_own_keywith|`None->Lwt.return_none|`TLS(_,_,`Password_)->Lwt.fail_with"OCaml-TLS cannot handle encrypted pem files"|`TLS(`Crt_file_pathcert,`Key_file_pathpriv_key,`No_password)->Conduit_lwt_tls.X509.private_of_pems~cert~priv_key>|=funcertificate->Some(`Singlecertificate))>>=funcertificates->lethostname=tryDomain_name.(host_exn(of_string_exnhostname))withInvalid_argumentmsg->lets=Printf.sprintf"couldn't convert %s to a [`host] Domain_name.t: %s"hostnamemsgininvalid_argsinConduit_lwt_tls.Client.connect?src:ctx.src?certificates~authenticator:ctx.tls_authenticatorhostnamesa>|=fun(fd,ic,oc)->letflow=TCP{fd;ip;port}in(flow,ic,oc)letconnect_with_openssl~ctx(`Hostnamehost_addr,`IPip,`Portport)=letsa=Unix.ADDR_INET(Ipaddr_unix.to_inet_addrip,port)inletctx_ssl=matchctx.tls_own_keywith|`None->ctx.ssl_ctx|`TLS(`Crt_file_pathcertfile,`Key_file_pathkeyfile,password)->letpassword=matchpasswordwith`No_password->None|`Passwordfn->Somefninletctx_ssl=Conduit_lwt_unix_ssl.Client.create_ctx~certfile~keyfile?password()inctx_sslinConduit_lwt_unix_ssl.Client.connect~ctx:ctx_ssl?src:ctx.src~hostname:host_addr~ip~verify:ctx.ssl_client_verifysa>>=fun(fd,ic,oc)->letflow=TCP{fd;ip;port}inLwt.return(flow,ic,oc)letconnect_with_default_tls~ctxtls_client_config=match!tls_librarywith|OpenSSL->connect_with_openssl~ctxtls_client_config|Native->connect_with_tls_native~ctxtls_client_config|No_tls->Lwt.fail_with"No SSL or TLS support compiled into Conduit"(** Main connection function *)letconnect~ctx(mode:client)=matchmodewith|`TCP(`IPip,`Portport)->letsa=Unix.ADDR_INET(Ipaddr_unix.to_inet_addrip,port)inSockaddr_client.connect?src:ctx.srcsa>>=fun(fd,ic,oc)->letflow=TCP{fd;ip;port}inLwt.return(flow,ic,oc)|`Unix_domain_socket(`Filepath)->Sockaddr_client.connect(Unix.ADDR_UNIXpath)>>=fun(fd,ic,oc)->letflow=Domain_socket{fd;path}inLwt.return(flow,ic,oc)|`TLSc->connect_with_default_tls~ctxc|`OpenSSLc->connect_with_openssl~ctxc|`TLS_nativec->connect_with_tls_native~ctxc|`Vchan_direct_->Lwt.fail_with"Vchan_direct not available on unix"|`Vchan_domain_socket_uuid->Lwt.fail_with"Vchan_domain_socket not implemented"letsockaddr_on_tcp_portctxport=letopenUnixinmatchctx.srcwith|Some(ADDR_UNIX_)->failwith"Cant listen to TCP on a domain socket"|Some(ADDR_INET(a,_))->(ADDR_INET(a,port),Ipaddr_unix.of_inet_addra)|None->(ADDR_INET(inet_addr_any,port),Ipaddr.(V4V4.any))letserve_with_openssl?timeout?stop~ctx~certfile~keyfile~pass~portcallback=letsockaddr,_=sockaddr_on_tcp_portctxportinletpassword=matchpasswith`No_password->None|`Passwordfn->SomefninConduit_lwt_unix_ssl.Server.init?password~certfile~keyfile?timeout?stopsockaddr(funaddrfdicoc->callback(flow_of_fdfdaddr)icoc)letserve_with_tls_native?timeout?stop~ctx~certfile~keyfile~pass~portcallback=letsockaddr,_=sockaddr_on_tcp_portctxportin(matchpasswith|`No_password->Lwt.return()|`Password_->Lwt.fail_with"OCaml-TLS cannot handle encrypted pem files")>>=fun()->Conduit_lwt_tls.Server.init~certfile~keyfile?timeout?stopsockaddr(funaddrfdicoc->callback(flow_of_fdfdaddr)icoc)letserve_with_default_tls?timeout?stop~ctx~certfile~keyfile~pass~portcallback=match!tls_librarywith|OpenSSL->serve_with_openssl?timeout?stop~ctx~certfile~keyfile~pass~portcallback|Native->serve_with_tls_native?timeout?stop~ctx~certfile~keyfile~pass~portcallback|No_tls->failwith"No SSL or TLS support compiled into Conduit"letserve?backlog?timeout?stop~on_exn~(ctx:ctx)~(mode:server)callback=letcallbackflowicoc=Lwt.catch(fun()->callbackflowicoc)(funexn->on_exnexn;Lwt.return_unit)inmatchmodewith|`TCP(`Portport)->letsockaddr,_=sockaddr_on_tcp_portctxportinSockaddr_server.init~on:(`Sockaddrsockaddr)?backlog?timeout?stopcallback|`TCP(`Sockets)->Sockaddr_server.init~on:(`Sockets)?backlog?timeout?stopcallback|`Unix_domain_socket(`Filepath)->letsockaddr=Unix.ADDR_UNIXpathinSockaddr_server.init~on:(`Sockaddrsockaddr)?backlog?timeout?stopcallback|`TLS(`Crt_file_pathcertfile,`Key_file_pathkeyfile,pass,`Portport)->serve_with_default_tls?timeout?stop~ctx~certfile~keyfile~pass~portcallback|`OpenSSL(`Crt_file_pathcertfile,`Key_file_pathkeyfile,pass,`Portport)->serve_with_openssl?timeout?stop~ctx~certfile~keyfile~pass~portcallback|`TLS_native(`Crt_file_pathcertfile,`Key_file_pathkeyfile,pass,`Portport)->serve_with_tls_native?timeout?stop~ctx~certfile~keyfile~pass~portcallback|`Vchan_direct_->Lwt.fail_with"Vchan_direct not implemented"|`Vchan_domain_socket_uuid->Lwt.fail_with"Vchan_domain_socket not implemented"|`Launchdname->letfns=Sockaddr_server.init~on:(`Sockets)?timeout?stopcallbackinConduit_lwt_launchd.activatefnnameletendp_of_flow=function|TCP{ip;port;_}->`TCP(ip,port)|Domain_socket{path;_}->`Unix_domain_socketpath|Vchan{domid;port}->`Vchan_direct(domid,port)(** Use the configuration of the server to interpret how to handle a particular
endpoint from the resolver into a concrete implementation of type [client] *)letendp_to_client~ctx:_(endp:Conduit.endp):clientLwt.t=matchendpwith|`TCP(ip,port)->Lwt.return(`TCP(`IPip,`Portport))|`Unix_domain_socketfile->Lwt.return(`Unix_domain_socket(`Filefile))|`Vchan_direct(domid,port)->Lwt.return(`Vchan_direct(`Domiddomid,`Portport))|`Vchan_domain_socket(name,port)->Lwt.return(`Vchan_domain_socket(`Domain_namename,`Portport))|`TLS(host,`TCP(ip,port))->Lwt.return(`TLS(`Hostnamehost,`IPip,`Portport))|`TLS(host,endp)->Lwt.fail_with(Printf.sprintf"TLS to non-TCP currently unsupported: host=%s endp=%s"host(Sexplib.Sexp.to_string_hum(Conduit.sexp_of_endpendp)))|`Unknownerr->Lwt.fail_with("resolution failed: "^err)letendp_to_server~ctx(endp:Conduit.endp)=matchendpwith|`Unix_domain_socketpath->Lwt.return(`Unix_domain_socket(`Filepath))|`TLS(_host,`TCP(_ip,port))->(matchctx.tls_own_keywith|`None->Lwt.fail_with"No TLS server key configured"|`TLS(`Crt_file_pathcrt,`Key_file_pathkey,pass)->Lwt.return(`TLS(`Crt_file_pathcrt,`Key_file_pathkey,pass,`Portport)))|`TCP(_ip,port)->Lwt.return(`TCP(`Portport))|`Vchan_direct_asmode->Lwt.returnmode|`Vchan_domain_socket_asmode->Lwt.returnmode|`TLS(_host,_)->Lwt.fail_with"TLS to non-TCP currently unsupported"|`Unknownerr->Lwt.fail_with("resolution failed: "^err)