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 
27 namespace ripple {
28 
29 bool
31 {
32  assert(!port.admin_ip.empty());
33  bool const passwordRequired =
34  (!port.admin_user.empty() || !port.admin_password.empty());
35 
36  return !passwordRequired ||
37  ((params["admin_password"].isString() &&
38  params["admin_password"].asString() == port.admin_password) &&
39  (params["admin_user"].isString() &&
40  params["admin_user"].asString() == port.admin_user));
41 }
42 
43 bool
45  beast::IP::Address const& remoteIp,
46  std::vector<beast::IP::Address> const& adminIp)
47 {
48  return std::find_if(
49  adminIp.begin(),
50  adminIp.end(),
51  [&remoteIp](beast::IP::Address const& ip) {
52  return ip.is_unspecified() || ip == remoteIp;
53  }) != adminIp.end();
54 }
55 
56 bool
58  Port const& port,
59  Json::Value const& params,
60  beast::IP::Address const& remoteIp)
61 {
62  return ipAllowed(remoteIp, port.admin_ip) &&
63  passwordUnrequiredOrSentCorrect(port, params);
64 }
65 
66 Role
68  Role const& required,
69  Port const& port,
70  Json::Value const& params,
71  beast::IP::Endpoint const& remoteIp,
72  boost::string_view const& user)
73 {
74  if (isAdmin(port, params, remoteIp.address()))
75  return Role::ADMIN;
76 
77  if (required == Role::ADMIN)
78  return Role::FORBID;
79 
80  if (ipAllowed(remoteIp.address(), port.secure_gateway_ip))
81  {
82  if (user.size())
83  return Role::IDENTIFIED;
84  return Role::PROXY;
85  }
86 
87  return Role::GUEST;
88 }
89 
93 bool
94 isUnlimited(Role const& role)
95 {
96  return role == Role::ADMIN || role == Role::IDENTIFIED;
97 }
98 
99 bool
101  Role const& required,
102  Port const& port,
103  Json::Value const& params,
104  beast::IP::Endpoint const& remoteIp,
105  std::string const& user)
106 {
107  return isUnlimited(requestRole(required, port, params, remoteIp, user));
108 }
109 
110 Resource::Consumer
112  Resource::Manager& manager,
113  beast::IP::Endpoint const& remoteAddress,
114  Role const& role,
115  boost::string_view const& user,
116  boost::string_view const& forwardedFor)
117 {
118  if (isUnlimited(role))
119  return manager.newUnlimitedEndpoint(remoteAddress);
120 
121  return manager.newInboundEndpoint(
122  remoteAddress, role == Role::PROXY, forwardedFor);
123 }
124 
125 static boost::string_view
126 extractIpAddrFromField(boost::string_view field)
127 {
128  // Lambda to trim leading and trailing spaces on the field.
129  auto trim = [](boost::string_view str) -> boost::string_view {
130  boost::string_view ret = str;
131 
132  // Only do the work if there's at least one leading space.
133  if (!ret.empty() && ret.front() == ' ')
134  {
135  std::size_t const firstNonSpace = ret.find_first_not_of(' ');
136  if (firstNonSpace == boost::string_view::npos)
137  // We know there's at least one leading space. So if we got
138  // npos, then it must be all spaces. Return empty string_view.
139  return {};
140 
141  ret = ret.substr(firstNonSpace);
142  }
143  // Trim trailing spaces.
144  if (!ret.empty())
145  {
146  // Only do the work if there's at least one trailing space.
147  if (unsigned char const c = ret.back();
148  c == ' ' || c == '\r' || c == '\n')
149  {
150  std::size_t const lastNonSpace = ret.find_last_not_of(" \r\n");
151  if (lastNonSpace == boost::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  boost::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 =
191  std::find_if_not(ret.begin(), ret.end(), [](unsigned char c) {
192  return std::isxdigit(c) || c == ':' || c == '.' || c == ' ';
193  });
194 
195  // If the string does not close with a ']', then it's not valid IPv6
196  // or IPv6 (dual).
197  if (closeBracket == ret.end() || (*closeBracket) != ']')
198  return {};
199 
200  // Remove trailing ']'
201  ret = ret.substr(0, closeBracket - ret.begin());
202  ret = trim(ret);
203  }
204  if (ret.empty())
205  return {};
206 
207  // If this is an IPv6 address (after unwrapping from square brackets),
208  // then there cannot be an appended port. In that case we're done.
209  {
210  // Skip any leading hex digits.
211  auto const colon =
212  std::find_if_not(ret.begin(), ret.end(), [](unsigned char c) {
213  return std::isxdigit(c) || c == ' ';
214  });
215 
216  // If the string starts with optional hex digits followed by a colon
217  // it's an IVv6 address. We're done.
218  if (colon == ret.end() || (*colon) == ':')
219  return ret;
220  }
221 
222  // If there's a port appended to the IP address, strip that by
223  // terminating at the colon.
224  if (std::size_t colon = ret.find(':'); colon != boost::string_view::npos)
225  ret = ret.substr(0, colon);
226 
227  return ret;
228 }
229 
230 boost::string_view
232 {
233  // Look for the Forwarded field in the request.
234  if (auto it = request.find(boost::beast::http::field::forwarded);
235  it != request.end())
236  {
237  auto ascii_tolower = [](char c) -> char {
238  return ((static_cast<unsigned>(c) - 65U) < 26) ? c + 'a' - 'A' : c;
239  };
240 
241  // Look for the first (case insensitive) "for="
242  static std::string const forStr{"for="};
243  char const* found = std::search(
244  it->value().begin(),
245  it->value().end(),
246  forStr.begin(),
247  forStr.end(),
248  [&ascii_tolower](char c1, char c2) {
249  return ascii_tolower(c1) == ascii_tolower(c2);
250  });
251 
252  if (found == it->value().end())
253  return {};
254 
255  found += forStr.size();
256 
257  // We found a "for=". Scan for the end of the IP address.
258  std::size_t const pos = [&found, &it]() {
259  std::size_t pos =
260  boost::string_view(found, it->value().end() - found)
261  .find_first_of(",;");
262  if (pos != boost::string_view::npos)
263  return pos;
264 
265  return it->value().size() - forStr.size();
266  }();
267 
268  return extractIpAddrFromField({found, pos});
269  }
270 
271  // Look for the X-Forwarded-For field in the request.
272  if (auto it = request.find("X-Forwarded-For"); it != request.end())
273  {
274  // The first X-Forwarded-For entry may be terminated by a comma.
275  std::size_t found = it->value().find(',');
276  if (found == boost::string_view::npos)
277  found = it->value().length();
278  return extractIpAddrFromField(it->value().substr(0, found));
279  }
280 
281  return {};
282 }
283 
284 } // 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:57
ripple::Port::admin_ip
std::vector< beast::IP::Address > admin_ip
Definition: Port.h:53
ripple::extractIpAddrFromField
static boost::string_view extractIpAddrFromField(boost::string_view field)
Definition: Role.cpp:126
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:30
std::vector< beast::IP::Address >
std::find_if
T find_if(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:111
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)
ripple::Role::IDENTIFIED
@ IDENTIFIED
ripple::Port::admin_user
std::string admin_user
Definition: Port.h:57
algorithm
beast::IP::Address
boost::asio::ip::address Address
Definition: IPAddress.h:41
ripple::Role::ADMIN
@ ADMIN
ripple::forwardedFor
boost::string_view forwardedFor(http_request_type const &request)
Definition: Role.cpp:231
ripple::Role::PROXY
@ PROXY
ripple::Port::secure_gateway_ip
std::vector< beast::IP::Address > secure_gateway_ip
Definition: Port.h:54
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:67
ripple::Port
Configuration information for a Server listening port.
Definition: Port.h:45
ripple::Port::admin_password
std::string admin_password
Definition: Port.h:58
ripple::isUnlimited
bool isUnlimited(Role const &role)
ADMIN and IDENTIFIED roles shall have unlimited resources.
Definition: Role.cpp:94
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::begin
T begin(T... args)
std::vector::empty
T empty(T... args)
ripple::ipAllowed
bool ipAllowed(beast::IP::Address const &remoteIp, std::vector< beast::IP::Address > const &adminIp)
True if remoteIp is in any of adminIp.
Definition: Role.cpp:44
std::size_t
beast::IP::Endpoint
A version-independent IP address and port combination.
Definition: IPEndpoint.h:38
std::vector::end
T end(T... args)
ripple::http_request_type
boost::beast::http::request< boost::beast::http::dynamic_body > http_request_type
Definition: Handshake.h:47
ripple::Role
Role
Indicates the level of administrative permission to grant.
Definition: Role.h:40
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