xrpld
Loading...
Searching...
No Matches
Role.cpp
1#include <xrpld/rpc/Role.h>
2
3#include <xrpl/beast/net/IPAddress.h>
4#include <xrpl/beast/net/IPEndpoint.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/json/json_value.h>
7#include <xrpl/resource/Consumer.h>
8#include <xrpl/resource/ResourceManager.h>
9#include <xrpl/server/Handoff.h>
10#include <xrpl/server/Port.h>
11
12#include <boost/asio/ip/impl/network_v4.ipp>
13#include <boost/asio/ip/impl/network_v6.ipp>
14#include <boost/asio/ip/network_v4.hpp>
15#include <boost/asio/ip/network_v6.hpp>
16#include <boost/beast/http/field.hpp>
17
18#include <algorithm>
19#include <cctype>
20#include <cstddef>
21#include <iterator>
22#include <string_view>
23#include <vector>
24
25namespace xrpl {
26
27bool
29{
30 XRPL_ASSERT(
31 !(port.adminNetsV4.empty() && port.adminNetsV6.empty()),
32 "xrpl::passwordUnrequiredOrSentCorrect : non-empty admin nets");
33 bool const passwordRequired = (!port.adminUser.empty() || !port.adminPassword.empty());
34
35 return !passwordRequired ||
36 ((params["admin_password"].isString() &&
37 params["admin_password"].asString() == port.adminPassword) &&
38 (params["admin_user"].isString() && params["admin_user"].asString() == port.adminUser));
39}
40
41bool
43 beast::IP::Address const& remoteIp,
46{
47 // To test whether the remoteIP is part of one of the configured
48 // subnets, first convert it to a subnet definition. For ipv4,
49 // this means appending /32. For ipv6, /128. Then based on protocol
50 // check for whether the resulting network is either a subnet of or
51 // equal to each configured subnet, based on boost::asio's reasoning.
52 // For example, 10.1.2.3 is a subnet of 10.1.2.0/24, but 10.1.2.0 is
53 // not. However, 10.1.2.0 is equal to the network portion of 10.1.2.0/24.
54
55 std::string addrString = remoteIp.to_string();
56 if (remoteIp.is_v4())
57 {
58 addrString += "/32";
59 auto ipNet = boost::asio::ip::make_network_v4(addrString);
60 for (auto const& net : nets4)
61 {
62 if (ipNet.is_subnet_of(net) || ipNet == net)
63 return true;
64 }
65 }
66 else
67 {
68 addrString += "/128";
69 auto ipNet = boost::asio::ip::make_network_v6(addrString);
70 for (auto const& net : nets6)
71 {
72 if (ipNet.is_subnet_of(net) || ipNet == net)
73 return true;
74 }
75 }
76
77 return false;
78}
79
80bool
81isAdmin(Port const& port, json::Value const& params, beast::IP::Address const& remoteIp)
82{
83 return ipAllowed(remoteIp, port.adminNetsV4, port.adminNetsV6) &&
85}
86
87Role
89 Role const& required,
90 Port const& port,
91 json::Value const& params,
92 beast::IP::Endpoint const& remoteIp,
94{
95 if (isAdmin(port, params, remoteIp.address()))
96 return Role::ADMIN;
97
98 if (required == Role::ADMIN)
99 return Role::FORBID;
100
101 if (ipAllowed(remoteIp.address(), port.secureGatewayNetsV4, port.secureGatewayNetsV6))
102 {
103 if (!user.empty())
104 return Role::IDENTIFIED;
105 return Role::PROXY;
106 }
107
108 return Role::GUEST;
109}
110
114bool
115isUnlimited(Role const& role)
116{
117 return role == Role::ADMIN || role == Role::IDENTIFIED;
118}
119
120bool
122 Role const& required,
123 Port const& port,
124 json::Value const& params,
125 beast::IP::Endpoint const& remoteIp,
126 std::string const& user)
127{
128 return isUnlimited(requestRole(required, port, params, remoteIp, user));
129}
130
131Resource::Consumer
133 Resource::Manager& manager,
134 beast::IP::Endpoint const& remoteAddress,
135 Role const& role,
136 std::string_view user,
138{
139 if (isUnlimited(role))
140 return manager.newUnlimitedEndpoint(remoteAddress);
141
142 return manager.newInboundEndpoint(remoteAddress, role == Role::PROXY, forwardedFor);
143}
144
145static std::string_view
147{
148 // Lambda to trim leading and trailing spaces on the field.
149 auto trim = [](std::string_view str) -> std::string_view {
150 std::string_view ret = str;
151
152 // Only do the work if there's at least one leading space.
153 if (!ret.empty() && ret.front() == ' ')
154 {
155 std::size_t const firstNonSpace = ret.find_first_not_of(' ');
156 if (firstNonSpace == std::string_view::npos)
157 {
158 // We know there's at least one leading space. So if we got
159 // npos, then it must be all spaces. Return empty string_view.
160 return {};
161 }
162
163 ret = ret.substr(firstNonSpace);
164 }
165 // Trim trailing spaces.
166 if (!ret.empty())
167 {
168 // Only do the work if there's at least one trailing space.
169 if (unsigned char const c = ret.back(); c == ' ' || c == '\r' || c == '\n')
170 {
171 std::size_t const lastNonSpace = ret.find_last_not_of(" \r\n");
172 if (lastNonSpace == std::string_view::npos)
173 {
174 // We know there's at least one leading space. So if we
175 // got npos, then it must be all spaces.
176 return {};
177 }
178
179 ret = ret.substr(0, lastNonSpace + 1);
180 }
181 }
182 return ret;
183 };
184
185 std::string_view ret = trim(field);
186 if (ret.empty())
187 return {};
188
189 // If there are surrounding quotes, strip them.
190 if (ret.front() == '"')
191 {
192 ret.remove_prefix(1);
193 if (ret.empty() || ret.back() != '"')
194 return {}; // Unbalanced double quotes.
195
196 ret.remove_suffix(1);
197
198 // Strip leading and trailing spaces that were inside the quotes.
199 ret = trim(ret);
200 }
201 if (ret.empty())
202 return {};
203
204 // If we have an IPv6 or IPv6 (dual) address wrapped in square brackets,
205 // then we need to remove the square brackets.
206 if (ret.front() == '[')
207 {
208 // Remove leading '['.
209 ret.remove_prefix(1);
210
211 // We may have an IPv6 address in square brackets. Scan up to the
212 // closing square bracket.
213 auto const closeBracket = std::ranges::find_if_not(ret, [](unsigned char c) {
214 return std::isxdigit(c) || c == ':' || c == '.' || c == ' ';
215 });
216
217 // If the string does not close with a ']', then it's not valid IPv6
218 // or IPv6 (dual).
219 if (closeBracket == ret.end() || (*closeBracket) != ']')
220 return {};
221
222 // Remove trailing ']'
223 ret = ret.substr(0, closeBracket - ret.begin());
224 ret = trim(ret);
225 }
226 if (ret.empty())
227 return {};
228
229 // If this is an IPv6 address (after unwrapping from square brackets),
230 // then there cannot be an appended port. In that case we're done.
231 {
232 // Skip any leading hex digits.
233 auto const colon = std::ranges::find_if_not(
234 ret, [](unsigned char c) { return std::isxdigit(c) || c == ' '; });
235
236 // If the string starts with optional hex digits followed by a colon
237 // it's an IVv6 address. We're done.
238 if (colon == ret.end() || (*colon) == ':')
239 return ret;
240 }
241
242 // If there's a port appended to the IP address, strip that by
243 // terminating at the colon.
244 if (std::size_t const colon = ret.find(':'); colon != std::string_view::npos)
245 ret = ret.substr(0, colon);
246
247 return ret;
248}
249
252{
253 // Look for the Forwarded field in the request.
254 if (auto it = request.find(boost::beast::http::field::forwarded); it != request.end())
255 {
256 auto asciiToLower = [](char c) -> char {
257 return ((static_cast<unsigned>(c) - 65U) < 26) ? c + 'a' - 'A' : c;
258 };
259
260 // Look for the first (case insensitive) "for=" at a directive
261 // boundary (start of value, or preceded by , ; or OWS).
262 static constexpr std::string_view kForStr{"for="};
263 auto const atFieldBoundary = [begin = it->value().begin()](auto p) {
264 return p == begin || p[-1] == ';' || p[-1] == ',' || p[-1] == ' ' || p[-1] == '\t';
265 };
266 auto found = it->value().begin();
267 while (true)
268 {
269 found = std::search(
270 found,
271 it->value().end(),
272 kForStr.begin(),
273 kForStr.end(),
274 [&asciiToLower](char c1, char c2) { return asciiToLower(c1) == asciiToLower(c2); });
275
276 if (found == it->value().end())
277 return {};
278
279 if (atFieldBoundary(found))
280 break;
281
282 ++found;
283 }
284
285 std::advance(found, kForStr.size());
286
287 // We found a "for=". Scan for the end of the IP address.
288 auto const end = it->value().end();
289 std::size_t const pos = [&found, &end]() {
290 std::size_t const pos =
291 std::string_view(found, std::distance(found, end)).find_first_of(",;");
292 if (pos != std::string_view::npos)
293 return pos;
294
295 return static_cast<std::size_t>(std::distance(found, end));
296 }();
297
298 return extractIpAddrFromField({found, pos});
299 }
300
301 // Look for the X-Forwarded-For field in the request.
302 if (auto it = request.find("X-Forwarded-For"); it != request.end())
303 {
304 // The first X-Forwarded-For entry may be terminated by a comma.
305 std::size_t found = it->value().find(',');
306 if (found == boost::string_view::npos)
307 found = it->value().length();
308 return extractIpAddrFromField(it->value().substr(0, found));
309 }
310
311 return {};
312}
313
314} // namespace xrpl
T advance(T... args)
T back(T... args)
T begin(T... args)
A version-independent IP address and port combination.
Definition IPEndpoint.h:17
Address const & address() const
Returns the address portion of this endpoint.
Definition IPEndpoint.h:54
Represents a JSON value.
Definition json_value.h:130
bool isString() const
std::string asString() const
Returns the unquoted string value.
Tracks load and resource consumption.
virtual Consumer newUnlimitedEndpoint(beast::IP::Endpoint const &address)=0
Create a new unlimited endpoint keyed by forwarded IP.
virtual Consumer newInboundEndpoint(beast::IP::Endpoint const &address)=0
Create a new endpoint keyed by inbound IP address or the forwarded IP if proxied.
T distance(T... args)
T empty(T... args)
T end(T... args)
T find_first_not_of(T... args)
T find_first_of(T... args)
T find_if_not(T... args)
T find_last_not_of(T... args)
T front(T... args)
boost::asio::ip::address Address
Definition IPAddress.h:19
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
Role requestRole(Role const &required, Port const &port, json::Value const &params, beast::IP::Endpoint const &remoteIp, std::string_view user)
Return the allowed privilege role.
Definition Role.cpp:88
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, std::string_view user, std::string_view forwardedFor)
Definition Role.cpp:132
std::string_view forwardedFor(http_request_type const &request)
Definition Role.cpp:251
Role
Indicates the level of administrative permission to grant.
Definition Role.h:24
@ IDENTIFIED
Definition Role.h:24
@ GUEST
Definition Role.h:24
@ ADMIN
Definition Role.h:24
@ FORBID
Definition Role.h:24
@ PROXY
Definition Role.h:24
bool ipAllowed(beast::IP::Address const &remoteIp, std::vector< boost::asio::ip::network_v4 > const &nets4, std::vector< boost::asio::ip::network_v6 > const &nets6)
True if remoteIp is in any of adminIp.
Definition Role.cpp:42
bool isAdmin(Port const &port, json::Value const &params, beast::IP::Address const &remoteIp)
Definition Role.cpp:81
static std::string_view extractIpAddrFromField(std::string_view field)
Definition Role.cpp:146
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition Handoff.h:12
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition Role.cpp:115
bool passwordUnrequiredOrSentCorrect(Port const &port, json::Value const &params)
Definition Role.cpp:28
T remove_prefix(T... args)
T remove_suffix(T... args)
T search(T... args)
T size(T... args)
Configuration information for a Server listening port.
Definition Port.h:28
std::vector< boost::asio::ip::network_v6 > adminNetsV6
Definition Port.h:36
std::vector< boost::asio::ip::network_v4 > secureGatewayNetsV4
Definition Port.h:37
std::vector< boost::asio::ip::network_v4 > adminNetsV4
Definition Port.h:35
std::string adminUser
Definition Port.h:41
std::vector< boost::asio::ip::network_v6 > secureGatewayNetsV6
Definition Port.h:38
std::string adminPassword
Definition Port.h:42
T substr(T... args)