Panda3D
bioPtr.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file bioPtr.cxx
10  * @author drose
11  * @date 2002-10-15
12  */
13 
14 #include "bioPtr.h"
15 
16 #ifdef HAVE_OPENSSL
17 
18 #include "urlSpec.h"
19 #include "config_downloader.h"
20 
21 #include "openSSLWrapper.h" // must be included before any other openssl.
22 #include <openssl/ssl.h>
23 
24 #ifdef _WIN32
25 #include <winsock2.h>
26 #else
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <netdb.h>
31 #include <fcntl.h>
32 #endif
33 
34 using std::string;
35 
36 #ifdef _WIN32
37 static string format_error() {
38  PVOID buffer;
39  DWORD len;
40  len = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
41  nullptr, WSAGetLastError(), 0, (LPTSTR)&buffer, 0, nullptr);
42  if (len == 0) {
43  return string("Unknown error message");
44  }
45 
46  const char *text = (const char *)buffer;
47  while (len > 0 && isspace(text[len - 1])) {
48  --len;
49  }
50 
51  string result(text, len);
52  LocalFree(buffer);
53  return result;
54 }
55 #else
56 #define format_error() strerror(errno)
57 #endif
58 
59 /**
60  * This flavor of the constructor automatically creates a socket BIO and feeds
61  * it the server and port name from the indicated URL. It doesn't call
62  * BIO_do_connect(), though.
63  */
64 BioPtr::
65 BioPtr(const URLSpec &url) : _connecting(false) {
66  if (url.get_scheme() == "file") {
67  // We're just reading a disk file.
68  string filename = URLSpec::unquote(url.get_path());
69 #ifdef _WIN32
70  // On Windows, we have to munge the filename specially, because it's been
71  // URL-munged. It might begin with a leading slash as well as a drive
72  // letter. Clean up that nonsense.
73  if (!filename.empty()) {
74  if (filename[0] == '/' || filename[0] == '\\') {
75  Filename fname = Filename::from_os_specific(filename.substr(1));
76  if (fname.is_local()) {
77  // Put the slash back on.
78  fname = string("/") + fname.get_fullpath();
79  }
80  filename = fname.to_os_specific();
81  }
82  }
83 #endif // _WIN32
84  _server_name = "";
85  _port = 0;
86  _bio = BIO_new_file(filename.c_str(), "rb");
87 
88  } else {
89  // A normal network-based URL. We don't use BIO_new_connect since it
90  // doesn't handle IPv6 properly.
91  _server_name = url.get_server();
92  _port = url.get_port();
93  _bio = nullptr;
94 
95  // These hints tell getaddrinfo what kind of address to return.
96  struct addrinfo hints, *res = nullptr;
97  memset(&hints, 0, sizeof(hints));
98  hints.ai_flags = AI_V4MAPPED | AI_ADDRCONFIG;
99  hints.ai_family = support_ipv6 ? AF_UNSPEC : AF_INET;
100  hints.ai_socktype = SOCK_STREAM;
101 
102  // Resolve the hostname or address string.
103  int result = getaddrinfo(_server_name.c_str(), nullptr, &hints, &res);
104  if (result != 0) {
105  const char *errmsg;
106 #ifndef _WIN32
107  if (result == EAI_SYSTEM && errno != 0) {
108  errmsg = strerror(errno);
109  } else
110 #endif
111  {
112  errmsg = gai_strerror(result);
113  }
114  downloader_cat.error()
115  << "Failed to resolve " << url.get_server() << ": " << errmsg << "\n";
116  return;
117  }
118  nassertv(res != nullptr && res->ai_addr != nullptr);
119 
120  // Store the real resolved address.
121  char buf[48];
122  buf[0] = 0;
123 #ifdef _WIN32
124  DWORD bufsize = sizeof(buf);
125  WSAAddressToStringA(res->ai_addr, res->ai_addrlen, nullptr, buf, &bufsize);
126 #else
127  if (res->ai_addr->sa_family == AF_INET) {
128  inet_ntop(AF_INET, (char *)&((sockaddr_in *)res->ai_addr)->sin_addr, buf, sizeof(buf));
129 
130  } else if (res->ai_addr->sa_family == AF_INET6) {
131  inet_ntop(AF_INET6, (char *)&((sockaddr_in6 *)res->ai_addr)->sin6_addr, buf, sizeof(buf));
132  }
133 #endif
134 
135  if (buf[0]) {
136  _server_name = buf;
137  }
138  if (downloader_cat.is_debug()) {
139  downloader_cat.debug()
140  << "Resolved " << url.get_server() << " to " << buf << "\n";
141  }
142 
143  // Create the socket.
144  int fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP);
145  if (fd < 0) {
146  downloader_cat.error()
147  << "Failed to create socket: " << format_error() << "\n";
148  _bio = nullptr;
149  freeaddrinfo(res);
150  return;
151  }
152 
153  // Store the address and length for later use in connect().
154  nassertv(res->ai_addrlen <= sizeof(_addr));
155  memcpy(&_addr, res->ai_addr, res->ai_addrlen);
156  _addrlen = res->ai_addrlen;
157  freeaddrinfo(res);
158 
159  // Also set the port we'd like to connect to.
160  if (_addr.ss_family == AF_INET) {
161  ((sockaddr_in &)_addr).sin_port = htons(_port);
162  } else if (_addr.ss_family == AF_INET6) {
163  ((sockaddr_in6 &)_addr).sin6_port = htons(_port);
164  }
165 
166  _bio = BIO_new_socket(fd, 1);
167  }
168 }
169 
170 /**
171  * Sets the non-blocking flag on the socket.
172  */
173 void BioPtr::
174 set_nbio(bool nbio) {
175  if (_bio == nullptr) {
176  return;
177  }
178 
179  int fd = -1;
180  BIO_get_fd(_bio, &fd);
181  nassertv_always(fd >= 0);
182 
183  BIO_socket_nbio(fd, nbio);
184 }
185 
186 /**
187  * Connects to the socket. Returns true on success.
188  */
189 bool BioPtr::
190 connect() {
191  if (_bio == nullptr) {
192  return false;
193  }
194 
195  int fd = -1;
196  BIO_get_fd(_bio, &fd);
197  nassertr(fd >= 0, false);
198 
199  int result;
200  if (_connecting) {
201  result = BIO_sock_error(fd);
202  } else {
203  result = ::connect(fd, (sockaddr *)&_addr, _addrlen);
204 
205  if (result != 0 && BIO_sock_should_retry(-1)) {
206  // It's still in progress; we should retry later. This causes
207  // should_retry() to return true.
208  BIO_set_flags(_bio, BIO_FLAGS_SHOULD_RETRY);
209  _connecting = true;
210  return false;
211  }
212  }
213  BIO_clear_retry_flags(_bio);
214  _connecting = false;
215 
216  if (result != 0) {
217  downloader_cat.warning()
218  << "Failed to connect to " << _server_name << " port " << _port
219  << ": " << format_error() << "\n";
220  return false;
221  }
222 
223  return true;
224 }
225 
226 /**
227  *
228  */
229 bool BioPtr::
230 should_retry() const {
231  return (_bio != nullptr) && BIO_should_retry(_bio);
232 }
233 
234 /**
235  *
236  */
237 BioPtr::
238 ~BioPtr() {
239  if (_bio != nullptr) {
240  if (downloader_cat.is_debug() && !_server_name.empty()) {
241  downloader_cat.debug()
242  << "Dropping connection to " << _server_name << " port " << _port << "\n";
243  }
244 
245  BIO_free_all(_bio);
246  _bio = nullptr;
247  }
248 }
249 
250 #endif // HAVE_OPENSSL
A container for a URL, e.g.
Definition: urlSpec.h:28
static std::string unquote(const std::string &source)
Reverses the operation of quote(): converts escaped characters of the form "%xx" to their ascii equiv...
Definition: urlSpec.cxx:801
get_scheme
Returns the scheme specified by the URL, or empty string if no scheme is specified.
Definition: urlSpec.h:93
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
std::string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:338
get_path
Returns the path specified by the URL, or "/" if no path is specified.
Definition: urlSpec.h:99
bool is_local() const
Returns true if the filename is local, e.g.
Definition: filename.I:549
get_port
Returns the port number specified by the URL, or the default port if not specified.
Definition: urlSpec.h:97
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
get_server
Returns the server name specified by the URL, if any.
Definition: urlSpec.h:96
std::string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1123
static Filename from_os_specific(const std::string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes,...
Definition: filename.cxx:328