123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479(* Ocsigen
* http://www.ocsigen.org
* Module accesscontrol.ml
* Copyright (C) 2007 Vincent Balat, Stéphane Glondu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)(* Filtering requests via the configuration file *)openOcsigen_libopenXmlletsection=Lwt_log.Section.make"ocsigen:ext:access-control"typecondition=Ocsigen_request.t->boolletips=letprefix=tryIpaddr.Prefix.of_string_exnswithIpaddr.Parse_error_->(tryletip=Ipaddr.of_string_exnsinIpaddr.Prefix.of_addripwith_->Ocsigen_extensions.badconfig"Bad ip/netmask [%s] in <ip> condition"s)infunri->letr=Ipaddr.Prefix.mem(Ocsigen_request.remote_ip_parsedri)prefixinifrthenLwt_log.ign_info_f~section"IP: %a matches %s"(fun()->Ocsigen_request.remote_ip)riselseLwt_log.ign_info_f~section"IP: %a does not match %s"(fun()->Ocsigen_request.remote_ip)ris;rletportportri=letr=Ocsigen_request.portri=portinifrthenLwt_log.ign_info_f~section"PORT = %d: true"portelseLwt_log.ign_info_f~section"PORT = %d: false (it is %a)"port(fun()ri->string_of_int(Ocsigen_request.portri))ri;rletsslri=letr=Ocsigen_request.sslriinifrthenLwt_log.ign_info~section"SSL: true"elseLwt_log.ign_info~section"SSL: false";rletheader~name~regexp:re=letregexp=tryNetstring_pcre.regexp("^"^re^"$")withFailure_->Ocsigen_extensions.badconfig"Bad regular expression [%s] in <header> condition"reinfunri->letr=List.exists(funa->letr=Netstring_pcre.string_matchregexpa0<>NoneinifrthenLwt_log.ign_info_f"HEADER: header %s matches %S"namere;r)(Ocsigen_request.header_multiri(Ocsigen_header.Name.of_stringname))inifnotrthenLwt_log.ign_info_f"HEADER: header %s does not match %S"namere;rletmethod_mri=letm'=Ocsigen_request.methriinlets=Cohttp.Code.string_of_methodminlets'=Cohttp.Code.string_of_methodm'inletr=m=m'inifrthenLwt_log.ign_info_f~section"METHOD: %s matches %s"s'selseLwt_log.ign_info_f~section"METHOD: %s does not match %s"s's;rletprotocolvri=letv'=Ocsigen_request.versionriinlets=Cohttp.Code.string_of_versionvinlets'=Cohttp.Code.string_of_versionv'inletr=v=v'inifrthenLwt_log.ign_info_f~section"PROTOCOL: %s matches %s"s'selseLwt_log.ign_info_f~section"PROTOCOL: %s does not match %s"s's;rletpath~regexp:s=letregexp=tryNetstring_pcre.regexp("^"^s^"$")withFailure_->Ocsigen_extensions.badconfig"Bad regular expression [%s] in <path> condition"sinfunri->letsps=Ocsigen_request.sub_path_stringriinletr=Netstring_pcre.string_matchregexpsps0<>NoneinifrthenLwt_log.ign_info_f~section"PATH: \"%s\" matches %S"spsselseLwt_log.ign_info_f~section"PATH: \"%s\" does not match %S"spss;rletand_subri=List.for_all(funcond->condri)subletor_subri=List.exists(funcond->condri)subletnot_subri=not(subri)letrecparse_condition=function|Element("ip",[("value",s)],[])->ips|Element(("ip"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("port",[("value",s)],[])->letp=tryint_of_stringswithFailure_->Ocsigen_extensions.badconfig"Bad port [%s] in <port> condition"sinportp|Element(("port"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("ssl",[],[])->ssl|Element(("ssl"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("header",[("name",name);("regexp",regexp)],[])->header~name~regexp|Element(("header"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("method",[("value",s)],[])->letm=Cohttp.Code.method_of_stringsinmethod_m|Element(("method"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("protocol",[("value",s)],[])->letv=Cohttp.Code.version_of_stringsinprotocolv|Element(("protocol"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("path",[("regexp",s)],[])->letregexp=tryNetstring_pcre.regexp("^"^s^"$")withFailure_->Ocsigen_extensions.badconfig"Bad regular expression [%s] in <path> condition"sinfunri->letsps=Ocsigen_request.sub_path_stringriinletr=Netstring_pcre.string_matchregexpsps0<>NoneinifrthenLwt_log.ign_info_f~section"PATH: \"%s\" matches %S"spsselseLwt_log.ign_info_f~section"PATH: \"%s\" does not match %S"spss;r|Element(("path"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("and",[],sub)->letsub=List.mapparse_conditionsubinfunri->List.for_all(funcond->condri)sub|Element(("and"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("or",[],sub)->letsub=List.mapparse_conditionsubinfunri->List.exists(funcond->condri)sub|Element(("or"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("not",[],[sub])->letsub=parse_conditionsubinfunri->not(subri)|Element(("not"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|_->Ocsigen_extensions.badconfig"Bad syntax for condition"(*****************************************************************************)(* Parsing filters *)letcomma_space_regexp=Netstring_pcre.regexp"\ *,\ *"letallow_forward_for_handler?(check_equal_ip=false)()=letapply({Ocsigen_extensions.request_info;_}asrequest)code=Lwt_log.ign_info~section"Allowed proxy";letrequest=letheader=Ocsigen_request.headerrequest_infoOcsigen_header.Name.x_forwarded_forinmatchheaderwith|Someheader->(matchOcsigen_lib.Netstring_pcre.splitcomma_space_regexpheaderwith|original_ip::proxies->letlast_proxy=List.lastproxiesinletproxy_ip=Ipaddr.of_string_exnlast_proxyinletequal_ip=proxy_ip=Ocsigen_request.remote_ip_parsedrequest_infoinifequal_ip||notcheck_equal_ipthen{requestwithOcsigen_extensions.request_info=Ocsigen_request.update~forward_ip:proxies~remote_ip:original_iprequest_info}else((* the announced ip of the proxy is not its real ip *)Lwt_log.ign_warning_f~section"X-Forwarded-For: host ip (%s) does not match the header (%s)"(Ocsigen_request.remote_iprequest_info)header;request)|_->Lwt_log.ign_info_f~section"Malformed X-Forwarded-For field: %s"header;request)|None->requestinLwt.return(Ocsigen_extensions.Ext_continue_with(request,Ocsigen_cookie_map.empty,code))infunction|Ocsigen_extensions.Req_found(request,resp)->applyrequest(Ocsigen_response.statusresp)|Ocsigen_extensions.Req_not_found(code,request)->applyrequestcodeletallow_forward_proto_handler=letapply({Ocsigen_extensions.request_info;_}asrequest)code=Lwt_log.ign_info~section"Allowed proxy for ssl";letrequest_info=letheader=Ocsigen_request.headerrequest_infoOcsigen_header.Name.x_forwarded_protoinmatchheaderwith|Someheader->(matchString.lowercase_asciiheaderwith|"http"->Ocsigen_request.update~ssl:falserequest_info|"https"->Ocsigen_request.update~ssl:truerequest_info|_->Lwt_log.ign_info_f~section"Malformed X-Forwarded-Proto field: %s"header;request_info)|None->request_infoinLwt.return(Ocsigen_extensions.Ext_continue_with({requestwithOcsigen_extensions.request_info},Ocsigen_cookie_map.empty,code))infunction|Ocsigen_extensions.Req_found(request,resp)->applyrequest(Ocsigen_response.statusresp)|Ocsigen_extensions.Req_not_found(code,request)->applyrequestcodeletparse_configparse_fun=function|Element("if",[],sub)->(letcondition,sub=matchsubwith|cond::q->parse_conditioncond,q|_->Ocsigen_extensions.badconfig"Bad condition in <if>"inletithen,sub=matchsubwith|Element("then",[],ithen)::q->parse_funithen,q|_->Ocsigen_extensions.badconfig"Bad <then> branch in <if>"inletielse,_sub=matchsubwith|Element("else",[],ielse)::([]asq)->parse_funielse,q|[]->parse_fun[],[]|_->Ocsigen_extensions.badconfig"Bad <else> branch in <if>"infunction|Ocsigen_extensions.Req_found(ri,_)|Ocsigen_extensions.Req_not_found(_,ri)->Lwt.return(ifconditionri.Ocsigen_extensions.request_infothen(Lwt_log.ign_info~section"COND: going into <then> branch";Ocsigen_extensions.Ext_sub_resultithen)else(Lwt_log.ign_info~section"COND: going into <else> branch, if any";Ocsigen_extensions.Ext_sub_resultielse)))|Element(("if"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("notfound",[],[])->fun_rs->Lwt_log.ign_info~section"NOT_FOUND: taking in charge 404";Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Not_found))|Element(("notfound"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("nextsite",[],[])->(function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_site(Ocsigen_cookie_map.empty,`Not_found)))|Element("nexthost",[],[])->(function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_host(Ocsigen_cookie_map.empty,`Not_found)))|Element(("nextsite"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("stop",[],[])->(function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Not_found)))|Element(("stop"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Xml.Element("forbidden",[],[])->fun_rs->Lwt_log.ign_info~section"FORBIDDEN: taking in charge 403";Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Forbidden))|Element(("forbidden"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("iffound",[],sub)->(letext=parse_funsubinfunction|Ocsigen_extensions.Req_found(_,_)->Lwt.return(Ocsigen_extensions.Ext_sub_resultext)|Ocsigen_extensions.Req_not_found(err,_ri)->Lwt.return(Ocsigen_extensions.Ext_nexterr))|Element(("iffound"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("ifnotfound",[],sub)->(letext=parse_funsubinfunction|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_sub_resultext))|Element("ifnotfound",[("code",s)],sub)->(letext=parse_funsubinletre=Netstring_pcre.regexp("^"^s^"$")infunction|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found(err,_ri)->ifleterr=string_of_intCohttp.Code.(code_of_status(err:>status_code))inNetstring_pcre.string_matchreerr0<>NonethenLwt.return(Ocsigen_extensions.Ext_sub_resultext)elseLwt.return(Ocsigen_extensions.Ext_nexterr))|Element(("ifnotfound"ass),_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("allow-forward-for",param,_)->letcheck_equal_ip=matchparamwith|[]->false|[("check-equal-ip",b)]->(trybool_of_stringbwithInvalid_argument_->Ocsigen_extensions.badconfig"Bad syntax for argument of tag allow-forward-for")|_->Ocsigen_extensions.badconfig"Bad syntax for argument of tag allow-forward-for"inallow_forward_for_handler~check_equal_ip()|Element("allow-forward-proto",_,_)->allow_forward_proto_handler|Element(t,_,_)->raise(Ocsigen_extensions.Bad_config_tag_for_extensiont)|_->Ocsigen_extensions.badconfig"(accesscontrol extension) Bad data"(* Registration of the extension for the config file: *)let()=Ocsigen_extensions.register~name:"accesscontrol"~fun_site:(fun_____->parse_config)()letif_conditionithenielsevhcip=function|Ocsigen_extensions.Req_found(ri,_)|Ocsigen_extensions.Req_not_found(_,ri)->Lwt.return(ifconditionri.Ocsigen_extensions.request_infothenOcsigen_extensions.Ext_sub_result(Ocsigen_extensions.compose(List.map(funi->ivhcip)ithen))elseOcsigen_extensions.Ext_sub_result(Ocsigen_extensions.compose(List.map(funi->ivhcip)ielse)))letiffoundinstrsvhcip=function|Ocsigen_extensions.Req_found(_,_)->Lwt.return(Ocsigen_extensions.Ext_sub_result(Ocsigen_extensions.compose(List.map(funi->ivhcip)instrs)))|Ocsigen_extensions.Req_not_found(err,_ri)->Lwt.return(Ocsigen_extensions.Ext_nexterr)letifnotfound?codeinstrsvhcip=letre=Option.map(funs->Netstring_pcre.regexp("^"^s^"$"))codeinfunction|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found(err,_)->(matchrewith|None->Lwt.return(Ocsigen_extensions.Ext_sub_result(Ocsigen_extensions.compose(List.map(funi->ivhcip)instrs)))|Somere->ifleterr=string_of_intCohttp.Code.(code_of_status(err:>status_code))inNetstring_pcre.string_matchreerr0<>NonethenLwt.return(Ocsigen_extensions.Ext_sub_result(Ocsigen_extensions.compose(List.map(funi->ivhcip)instrs)))elseLwt.return(Ocsigen_extensions.Ext_nexterr))letnotfound____=Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Not_found))letnextsite___=function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_site(Ocsigen_cookie_map.empty,`Not_found))letnexthost___=function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_host(Ocsigen_cookie_map.empty,`Not_found))letstop___=function|Ocsigen_extensions.Req_found(_,r)->Lwt.return(Ocsigen_extensions.Ext_found_stop(fun()->Lwt.returnr))|Ocsigen_extensions.Req_not_found_->Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Not_found))letforbidden____=Lwt.return(Ocsigen_extensions.Ext_stop_all(Ocsigen_cookie_map.empty,`Forbidden))letallow_forward_for?check_equal_ip()___=allow_forward_for_handler?check_equal_ip()letallow_forward_proto()___=allow_forward_proto_handler