123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439(* 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"letrecparse_condition=function|Element("ip",["value",s],[])->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"sin(funri->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;r)|Element("ip"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("port",["value",s],[])->letport=tryint_of_stringswithFailure_->Ocsigen_extensions.badconfig"Bad port [%s] in <port> condition"sin(funri->letr=Ocsigen_request.portri=portinifrthenLwt_log.ign_info_f~section"PORT: %d accepted"portelseLwt_log.ign_info_f~section"PORT: %a not accepted (%d expected)"(fun()ri->string_of_int(Ocsigen_request.portri))riport;r)|Element("port"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("ssl",[],[])->(funri->letr=Ocsigen_request.sslriinifrthenLwt_log.ign_info~section"SSL: accepted"elseLwt_log.ign_info~section"SSL: not accepted";r)|Element("ssl"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("header",["name",name;"regexp",reg],[])->letregexp=tryNetstring_pcre.regexp("^"^reg^"$")withFailure_->Ocsigen_extensions.badconfig"Bad regular expression [%s] in <header> condition"regin(funri->letr=List.exists(funa->letr=Netstring_pcre.string_matchregexpa0<>NoneinifrthenLwt_log.ign_info_f"HEADER: header %s matches %S"namereg;r)(Ocsigen_request.header_multiri(Ocsigen_header.Name.of_stringname))inifnotrthenLwt_log.ign_info_f"HEADER: header %s does not match %S"namereg;r)|Element("header"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("method",["value",s],[])->funri->letm=Cohttp.Code.method_of_stringsandm'=Ocsigen_request.methriinlets'=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;r|Element("method"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("protocol",["value",s],[])->funri->letv=Cohttp.Code.version_of_stringsandv'=Ocsigen_request.versionriinlets'=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;r|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_conditionsubin(funri->List.for_all(funcond->condri)sub)|Element("and"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("or",[],sub)->letsub=List.mapparse_conditionsubin(funri->List.exists(funcond->condri)sub)|Element("or"ass,_,_)->Ocsigen_extensions.badconfig"Bad syntax for tag %s"s|Element("not",[],[sub])->letsub=parse_conditionsubin(funri->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"\ *,\ *"letparse_configparse_fun=function|Element("if",[],sub)->let(condition,sub)=matchsubwith|cond::q->parse_conditioncond,q|_->Ocsigen_extensions.badconfig"Bad condition in <if>"inlet(ithen,sub)=matchsubwith|Element("then",[],ithen)::q->parse_funithen,q|_->Ocsigen_extensions.badconfig"Bad <then> branch in <if>"inlet(ielse,_sub)=matchsubwith|Element("else",[],ielse)::([]asq)->parse_funielse,q|[]->(parse_fun[],[])|_->Ocsigen_extensions.badconfig"Bad <else> branch in <if>"in(function|Ocsigen_extensions.Req_found(ri,_)|Ocsigen_extensions.Req_not_found(_,ri)->Lwt.return(ifconditionri.Ocsigen_extensions.request_infothenbeginLwt_log.ign_info~section"COND: going into <then> branch";Ocsigen_extensions.Ext_sub_resultithenendelsebeginLwt_log.ign_info~section"COND: going into <else> branch, if any";Ocsigen_extensions.Ext_sub_resultielseend))|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_funsubin(function|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_funsubin(function|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_funsubinletr=Netstring_pcre.regexp("^"^s^"$")in(function|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_matchrerr0<>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,_)->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_infoinletneed_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"inifequal_ip||notneed_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))in(function|Ocsigen_extensions.Req_found(request,resp)->applyrequest(Ocsigen_response.statusresp)|Ocsigen_extensions.Req_not_found(code,request)->applyrequestcode)|Element("allow-forward-proto",_,_)->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))in(function|Ocsigen_extensions.Req_found(request,resp)->applyrequest(Ocsigen_response.statusresp)|Ocsigen_extensions.Req_not_found(code,request)->applyrequestcode)|Element(t,_,_)->raise(Ocsigen_extensions.Bad_config_tag_for_extensiont)|_->Ocsigen_extensions.badconfig"(accesscontrol extension) Bad data"(* Registration of the extension *)let()=Ocsigen_extensions.register~name:"accesscontrol"~fun_site:(fun_____->parse_config)()