diff --git a/developer_tools/vpnserver-jsonrpc-clients/README.html b/developer_tools/vpnserver-jsonrpc-clients/README.html index 2d41f959..d7a7ea47 100644 --- a/developer_tools/vpnserver-jsonrpc-clients/README.html +++ b/developer_tools/vpnserver-jsonrpc-clients/README.html @@ -30,6 +30,7 @@

JSON-RPC specification

You must use HTTPS 1.1 POST method to call each of JSON-RPC APIs.
diff --git a/developer_tools/vpnserver-jsonrpc-clients/README.md b/developer_tools/vpnserver-jsonrpc-clients/README.md index 1ea33f9f..23ab02d6 100644 --- a/developer_tools/vpnserver-jsonrpc-clients/README.md +++ b/developer_tools/vpnserver-jsonrpc-clients/README.md @@ -25,6 +25,7 @@ https://:/api/ - Older versions of SoftEther VPN before June 2019 don't support JSON-RPC APIs. - If you want to completely disable the JSON-RPC on your VPN Server, set the `DisableJsonRpcWebApi` variable to `true` on the `vpn_server.config`. + - You may also restrict access to JSON-RPC API to a specific subnet, e.g. your internal network, by setting the `JsonRpcWebApiAllowedSubnet` variable to, for example, `192.168.0.0/16`. ### JSON-RPC specification diff --git a/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt b/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt index aed5c743..45be851d 100644 --- a/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt +++ b/developer_tools/vpnserver-jsonrpc-codegen/Templates/doc.txt @@ -25,6 +25,7 @@ https://:/api/ - Older versions of SoftEther VPN before June 2019 don't support JSON-RPC APIs. - If you want to completely disable the JSON-RPC on your VPN Server, set the `DisableJsonRpcWebApi` variable to `true` on the `vpn_server.config`. + - You may also restrict access to JSON-RPC API to a specific subnet, e.g. your internal network, by setting the `JsonRpcWebApiAllowedSubnet` variable to, for example, `192.168.0.0/16`. ### JSON-RPC specification diff --git a/src/Cedar/Protocol.c b/src/Cedar/Protocol.c index a0da6c70..f20e8c87 100644 --- a/src/Cedar/Protocol.c +++ b/src/Cedar/Protocol.c @@ -5740,6 +5740,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) UINT num = 0, max = 19; SERVER *server; char *vpn_http_target = HTTP_VPN_TARGET2; + bool disableJsonRpcWebApi; // Validate arguments if (c == NULL) { @@ -5750,6 +5751,15 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) s = c->FirstSock; + disableJsonRpcWebApi = server->DisableJsonRpcWebApi; + if (!disableJsonRpcWebApi && !IsZeroIP(&server->JsonRpcWebApiAllowedSubnetAddr) + && !IsZeroIP(&server->JsonRpcWebApiAllowedSubnetMask)) { + // restrict JSON-RPC Web API to specified subnet only + if (!IsInSameNetwork(&s->RemoteIP, &server->JsonRpcWebApiAllowedSubnetAddr, &server->JsonRpcWebApiAllowedSubnetMask)) { + disableJsonRpcWebApi = true; + } + } + while (true) { bool not_found_error = false; @@ -5782,7 +5792,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) // Receive the data since it's POST data_size = GetContentLength(h); - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StrCmpi(h->Target, "/api") == 0 || StrCmpi(h->Target, "/api/") == 0) { @@ -5868,7 +5878,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) } else if (StrCmpi(h->Method, "OPTIONS") == 0) { - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StrCmpi(h->Target, "/api") == 0 || StrCmpi(h->Target, "/api/") == 0 || StartWith(h->Target, "/admin")) { @@ -5939,7 +5949,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) BUF *b = NULL; *error_detail_str = "HTTP_ROOT"; - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { b = ReadDump("|wwwroot/index.html"); } @@ -6019,7 +6029,7 @@ bool ServerDownloadSignature(CONNECTION *c, char **error_detail_str) if (b == false) { - if (server->DisableJsonRpcWebApi == false) + if (disableJsonRpcWebApi == false) { if (StartWith(h->Target, "/api?") || StartWith(h->Target, "/api/") || StrCmpi(h->Target, "/api") == 0) { diff --git a/src/Cedar/Server.c b/src/Cedar/Server.c index 1aad0d94..6eb53d44 100644 --- a/src/Cedar/Server.c +++ b/src/Cedar/Server.c @@ -30,6 +30,7 @@ #include "Mayaqua/Internat.h" #include "Mayaqua/Memory.h" #include "Mayaqua/Microsoft.h" +#include "Mayaqua/Network.h" #include "Mayaqua/Object.h" #include "Mayaqua/OS.h" #include "Mayaqua/Pack.h" @@ -6032,6 +6033,15 @@ void SiLoadServerCfg(SERVER *s, FOLDER *f) // Disable JSON-RPC Web API s->DisableJsonRpcWebApi = CfgGetBool(f, "DisableJsonRpcWebApi"); + char tmpaddr[MAX_PATH]; + if (CfgGetStr(f, "JsonRpcWebApiAllowedSubnet", tmpaddr, sizeof(tmpaddr))) { + IP _subnet, _mask; + if (ParseIpAndMask46(tmpaddr, &_subnet, &_mask)) { + s->JsonRpcWebApiAllowedSubnetAddr = _subnet; + s->JsonRpcWebApiAllowedSubnetMask = _mask; + } + } + // Bits of Diffie-Hellman parameters c->DhParamBits = CfgGetInt(f, "DhParamBits"); if (c->DhParamBits == 0) @@ -6365,6 +6375,11 @@ void SiWriteServerCfg(FOLDER *f, SERVER *s) // Disable JSON-RPC Web API CfgAddBool(f, "DisableJsonRpcWebApi", s->DisableJsonRpcWebApi); + + char tmpaddr[MAX_PATH]; + IPAndMaskToStr(tmpaddr, sizeof(tmpaddr), + &s->JsonRpcWebApiAllowedSubnetAddr, &s->JsonRpcWebApiAllowedSubnetMask); + CfgAddStr(f, "JsonRpcWebApiAllowedSubnet", tmpaddr); } Unlock(c->lock); } diff --git a/src/Cedar/Server.h b/src/Cedar/Server.h index 01fbddf7..82e8a22c 100644 --- a/src/Cedar/Server.h +++ b/src/Cedar/Server.h @@ -276,6 +276,9 @@ struct SERVER IP ListenIP; // Listen IP bool StrictSyslogDatetimeFormat; // Make syslog datetime format strict RFC3164 bool DisableJsonRpcWebApi; // Disable JSON-RPC Web API + + IP JsonRpcWebApiAllowedSubnetAddr; // If set, allow access to JSON-RPC Web API from + IP JsonRpcWebApiAllowedSubnetMask; // this subnet only }; diff --git a/src/Mayaqua/Network.c b/src/Mayaqua/Network.c index 52da1246..11e439d9 100644 --- a/src/Mayaqua/Network.c +++ b/src/Mayaqua/Network.c @@ -6993,6 +6993,18 @@ void IPToStr6Inner(char *str, IP *ip) } } +// Format IP and subnet mask as "/" +void IPAndMaskToStr(char *str, UINT size, IP *ip, IP *subnet) +{ + int iplen; + UINT masksize; + + IPToStr(str, size, ip); + iplen = StrLen(str); + masksize = SubnetMaskToInt(subnet); + Format(str + iplen, size - iplen, "/%d", masksize); +} + // Convert the string to an IP address bool StrToIP6(IP *ip, char *str) { diff --git a/src/Mayaqua/Network.h b/src/Mayaqua/Network.h index 39e3f5fc..6ea043a9 100644 --- a/src/Mayaqua/Network.h +++ b/src/Mayaqua/Network.h @@ -1289,6 +1289,7 @@ void IPToStr6(char *str, UINT size, IP *ip); void IP6AddrToStr(char *str, UINT size, IPV6_ADDR *addr); void IPToStr6Array(char *str, UINT size, UCHAR *bytes); void IPToStr6Inner(char *str, IP *ip); +void IPAndMaskToStr(char *str, UINT size, IP *ip, IP *subnet); void IntToSubnetMask6(IP *ip, UINT i); void IPAnd6(IP *dst, IP *a, IP *b); void GetAllRouterMulticastAddress6(IP *ip);