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