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