123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394(*
* 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](** Configuration fragment for a listening TLS server *)typeserver_tls_config=[`Crt_file_pathofstring]*[`Key_file_pathofstring]*[`Passwordofbool->string|`No_password]*[`Portofint][@@derivingsexp](** Set of ways to create TCP servers *)typetcp_config=[|`Portofint|`SocketofLwt_unix.file_descrsexp_opaque][@@derivingsexp](** Set of supported listening mechanisms that are supported by this module. *)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]typetls_server_key=[|`None|`TLSof[`Crt_file_pathofstring]*[`Key_file_pathofstring]*[`Passwordofbool->string|`No_password]][@@derivingsexp]typectx={src:Unix.sockaddroption;tls_server_key:tls_server_key;}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_server_key]((matchctx.srcwith|None->None|Somesa->Some(string_of_unix_sockaddrsa)),ctx.tls_server_key)typetcp_flow={fd:Lwt_unix.file_descrsexp_opaque;ip:Ipaddr_sexp.t;port:int;}[@@derivingsexp]typedomain_flow={fd:Lwt_unix.file_descrsexp_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={src=None;tls_server_key=`None}letinit?src?(tls_server_key=`None)()=matchsrcwith|None->Lwt.return{src=None;tls_server_key}|Somehost->letopenUnixinLwt_unix.getaddrinfohost"0"[AI_PASSIVE;AI_SOCKTYPESOCK_STREAM]>>=function|{ai_addr;_}::_->Lwt.return{src=Someai_addr;tls_server_key}|[]->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)inConduit_lwt_tls.Client.connect?src:ctx.srchostnamesa>|=fun(fd,ic,oc)->letflow=TCP{fd;ip;port}in(flow,ic,oc)letconnect_with_openssl~ctx(`Hostnamehostname,`IPip,`Portport)=letsa=Unix.ADDR_INET(Ipaddr_unix.to_inet_addrip,port)inConduit_lwt_unix_ssl.Client.connect?src:ctx.src~hostnamesa>>=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,ip=sockaddr_on_tcp_portctxportinletpassword=matchpasswith|`No_password->None|`Passwordfn->SomefninConduit_lwt_unix_ssl.Server.init?password~certfile~keyfile?timeout?stopsockaddr(funfdicoc->callback(TCP{fd;ip;port})icoc)letserve_with_tls_native?timeout?stop~ctx~certfile~keyfile~pass~portcallback=letsockaddr,ip=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(funfdicoc->callback(TCP{fd;ip;port})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=(funexn->!Lwt.async_exception_hookexn))~(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)->beginLwt.fail_with(Printf.sprintf"TLS to non-TCP currently unsupported: host=%s endp=%s"host(Sexplib.Sexp.to_string_hum(Conduit.sexp_of_endpendp)))end|`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))->beginmatchctx.tls_server_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))end|`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)