rippled
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 <ripple/rpc/Role.h>
21 #include <boost/beast/core/string.hpp>
22 #include <boost/beast/http/field.hpp>
23 #include <boost/beast/http/rfc7230.hpp>
24 #include <boost/utility/string_view.hpp>
25 #include <algorithm>
26 #include <tuple>
27 
28 namespace ripple {
29 
30 bool
32 {
33  assert(!(port.admin_nets_v4.empty() && port.admin_nets_v6.empty()));
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 
44 bool
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 
83 bool
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) &&
90  passwordUnrequiredOrSentCorrect(port, params);
91 }
92 
93 Role
95  Role const& required,
96  Port const& port,
97  Json::Value const& params,
98  beast::IP::Endpoint const& remoteIp,
99  boost::string_view const& user)
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 
123 bool
124 isUnlimited(Role const& role)
125 {
126  return role == Role::ADMIN || role == Role::IDENTIFIED;
127 }
128 
129 bool
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 
140 Resource::Consumer
142  Resource::Manager& manager,
143  beast::IP::Endpoint const& remoteAddress,
144  Role const& role,
145  boost::string_view const& user,
146  boost::string_view const& forwardedFor)
147 {
148  if (isUnlimited(role))
149  return manager.newUnlimitedEndpoint(remoteAddress);
150 
151  return manager.newInboundEndpoint(
152  remoteAddress, role == Role::PROXY, forwardedFor);
153 }
154 
155 static boost::string_view
156 extractIpAddrFromField(boost::string_view field)
157 {
158  // Lambda to trim leading and trailing spaces on the field.
159  auto trim = [](boost::string_view str) -> boost::string_view {
160  boost::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 == boost::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 == boost::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  boost::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 != boost::string_view::npos)
255  ret = ret.substr(0, colon);
256 
257  return ret;
258 }
259 
260 boost::string_view
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 =
290  boost::string_view(found, it->value().end() - found)
291  .find_first_of(",;");
292  if (pos != boost::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
ripple::Resource::Manager::newInboundEndpoint
virtual Consumer newInboundEndpoint(beast::IP::Endpoint const &address)=0
Create a new endpoint keyed by inbound IP address or the forwarded IP if proxied.
ripple::isAdmin
bool isAdmin(Port const &port, Json::Value const &params, beast::IP::Address const &remoteIp)
Definition: Role.cpp:84
ripple::extractIpAddrFromField
static boost::string_view extractIpAddrFromField(boost::string_view field)
Definition: Role.cpp:156
std::string
STL class.
Json::Value::isString
bool isString() const
Definition: json_value.cpp:1009
ripple::passwordUnrequiredOrSentCorrect
bool passwordUnrequiredOrSentCorrect(Port const &port, Json::Value const &params)
Definition: Role.cpp:31
std::vector< boost::asio::ip::network_v4 >
std::find_if_not
T find_if_not(T... args)
ripple::requestInboundEndpoint
Resource::Consumer requestInboundEndpoint(Resource::Manager &manager, beast::IP::Endpoint const &remoteAddress, Role const &role, boost::string_view const &user, boost::string_view const &forwardedFor)
Definition: Role.cpp:141
ripple::Port::secure_gateway_nets_v6
std::vector< boost::asio::ip::network_v6 > secure_gateway_nets_v6
Definition: Port.h:59
beast::IP::Endpoint::address
Address const & address() const
Returns the address portion of this endpoint.
Definition: IPEndpoint.h:76
std::search
T search(T... args)
tuple
ripple::Role::IDENTIFIED
@ IDENTIFIED
ripple::Port::admin_user
std::string admin_user
Definition: Port.h:62
algorithm
ripple::Port::admin_nets_v6
std::vector< boost::asio::ip::network_v6 > admin_nets_v6
Definition: Port.h:57
beast::IP::Address
boost::asio::ip::address Address
Definition: IPAddress.h:41
ripple::Role::ADMIN
@ ADMIN
ripple::Port::secure_gateway_nets_v4
std::vector< boost::asio::ip::network_v4 > secure_gateway_nets_v4
Definition: Port.h:58
ripple::forwardedFor
boost::string_view forwardedFor(http_request_type const &request)
Definition: Role.cpp:261
ripple::Port::admin_nets_v4
std::vector< boost::asio::ip::network_v4 > admin_nets_v4
Definition: Port.h:56
ripple::Role::PROXY
@ PROXY
ripple::Role::FORBID
@ FORBID
ripple::requestRole
Role requestRole(Role const &required, Port const &port, Json::Value const &params, beast::IP::Endpoint const &remoteIp, boost::string_view const &user)
Return the allowed privilege role.
Definition: Role.cpp:94
ripple::Port
Configuration information for a Server listening port.
Definition: Port.h:48
ripple::Port::admin_password
std::string admin_password
Definition: Port.h:63
ripple::isUnlimited
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition: Role.cpp:124
ripple::Resource::Manager
Tracks load and resource consumption.
Definition: ResourceManager.h:36
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::Role::GUEST
@ GUEST
ripple::Resource::Manager::newUnlimitedEndpoint
virtual Consumer newUnlimitedEndpoint(beast::IP::Endpoint const &address)=0
Create a new unlimited endpoint keyed by forwarded IP.
std::vector::empty
T empty(T... args)
std::size_t
beast::IP::Endpoint
A version-independent IP address and port combination.
Definition: IPEndpoint.h:38
ripple::http_request_type
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handshake.h:47
ripple::ipAllowed
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
ripple::Role
Role
Indicates the level of administrative permission to grant.
Definition: Role.h:43
Json::Value
Represents a JSON value.
Definition: json_value.h:145
Json::Value::asString
std::string asString() const
Returns the unquoted string value.
Definition: json_value.cpp:469