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