1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
3 Copyright (C) 2009 Red Hat, Inc.
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, see <http://www.gnu.org/licenses/>.
25 #include <openssl/x509.h>
26 #include <openssl/x509v3.h>
27 #include <spice/protocol.h>
28 #include "common/ssl_verify.h"
34 #include "platform_utils.h"
36 static void SPICE_GNUC_NORETURN ssl_error()
38 unsigned long last_error = ERR_peek_last_error();
40 ERR_print_errors_fp(stderr);
41 THROW_ERR(SPICEC_ERROR_CODE_SSL_ERROR, "SSL Error: %s", ERR_error_string(last_error, NULL));
45 : _peer (INVALID_SOCKET)
57 void RedPeer::cleanup()
69 if (_peer != INVALID_SOCKET) {
71 _peer = INVALID_SOCKET;
75 void RedPeer::connect_to_peer(const char* host, int portnr)
77 struct addrinfo ai, *result = NULL, *e;
78 char uaddr[INET6_ADDRSTRLEN+1];
79 char uport[33], port[33];
80 int err = 0, rc, no_delay = 1;
81 ASSERT(_ctx == NULL && _ssl == NULL && _peer == INVALID_SOCKET);
83 memset(&ai,0, sizeof(ai));
84 ai.ai_flags = AI_CANONNAME;
86 ai.ai_flags |= AI_ADDRCONFIG;
88 ai.ai_family = PF_UNSPEC;
89 ai.ai_socktype = SOCK_STREAM;
90 snprintf(port, sizeof(port), "%d", portnr);
91 rc = getaddrinfo(host, port, &ai, &result);
93 THROW_ERR(SPICEC_ERROR_CODE_GETHOSTBYNAME_FAILED, "cannot resolve host address %s", host);
96 _peer = INVALID_SOCKET;
97 for (e = result; e != NULL; e = e->ai_next) {
98 if ((_peer = socket(e->ai_family, e->ai_socktype, e->ai_protocol)) == INVALID_SOCKET) {
99 int err = sock_error();
100 THROW_ERR(SPICEC_ERROR_CODE_SOCKET_FAILED, "failed to create socket: %s (%d)",
101 sock_err_message(err), err);
103 if (setsockopt(_peer, IPPROTO_TCP, TCP_NODELAY, (const char*)&no_delay, sizeof(no_delay)) ==
105 LOG_WARN("set TCP_NODELAY failed");
108 getnameinfo((struct sockaddr*)e->ai_addr, e->ai_addrlen,
109 uaddr,INET6_ADDRSTRLEN, uport,32,
110 NI_NUMERICHOST | NI_NUMERICSERV);
111 DBG(0, "Trying %s %s", uaddr, uport);
113 if (::connect(_peer, e->ai_addr, e->ai_addrlen) == SOCKET_ERROR) {
115 LOG_INFO("Connect failed: %s (%d)",
116 sock_err_message(err), err);
118 _peer = INVALID_SOCKET;
121 DBG(0, "Connected to %s %s", uaddr, uport);
125 freeaddrinfo(result);
126 if (_peer == INVALID_SOCKET) {
127 THROW_ERR(SPICEC_ERROR_CODE_CONNECT_FAILED, "failed to connect: %s (%d)",
128 sock_err_message(err), err);
138 void RedPeer::connect_unsecure(const char* host, int portnr)
140 connect_to_peer(host, portnr);
141 ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
142 LOG_INFO("Connected to %s %d", host, portnr);
145 void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
148 SPICE_SSL_VERIFY_OP auth_flags;
149 SpiceOpenSSLVerify* verify = NULL;
150 int portnr = options.secure_port;
152 connect_to_peer(host, portnr);
153 ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
154 LOG_INFO("Connected to %s %d", host, portnr);
157 #if OPENSSL_VERSION_NUMBER >= 0x10000000L
158 const SSL_METHOD *ssl_method = TLSv1_method();
160 SSL_METHOD *ssl_method = TLSv1_method();
162 _ctx = SSL_CTX_new(ssl_method);
167 auth_flags = options.host_auth.type_flags;
168 if ((auth_flags & SPICE_SSL_VERIFY_OP_HOSTNAME) ||
169 (auth_flags & SPICE_SSL_VERIFY_OP_SUBJECT)) {
170 std::string CA_file = options.host_auth.CA_file;
171 ASSERT(!CA_file.empty());
173 return_code = SSL_CTX_load_verify_locations(_ctx, CA_file.c_str(), NULL);
174 if (return_code != 1) {
175 if (auth_flags & SPICE_SSL_VERIFY_OP_PUBKEY) {
176 LOG_WARN("SSL_CTX_load_verify_locations failed, CA_file=%s. "
177 "only pubkey authentication is active", CA_file.c_str());
178 auth_flags = SPICE_SSL_VERIFY_OP_PUBKEY;
181 LOG_ERROR("SSL_CTX_load_verify_locations failed CA_file=%s", CA_file.c_str());
187 return_code = SSL_CTX_set_cipher_list(_ctx, options.ciphers.c_str());
188 if (return_code != 1) {
189 LOG_ERROR("SSL_CTX_set_cipher_list failed, ciphers=%s", options.ciphers.c_str());
193 _ssl = SSL_new(_ctx);
195 THROW("create ssl failed");
198 verify = spice_openssl_verify_new(
201 (char*)&options.host_auth.host_pubkey[0],
202 options.host_auth.host_pubkey.size(),
203 options.host_auth.host_subject.c_str());
205 BIO* sbio = BIO_new_socket(_peer, BIO_NOCLOSE);
207 THROW("alloc new socket bio failed");
210 SSL_set_bio(_ssl, sbio, sbio);
212 return_code = SSL_connect(_ssl);
213 if (return_code <= 0) {
214 int ssl_error_code = SSL_get_error(_ssl, return_code);
215 LOG_ERROR("failed to connect w/SSL, ssl_error %s",
216 ERR_error_string(ssl_error_code, NULL));
221 spice_openssl_verify_free(verify);
226 spice_openssl_verify_free(verify);
229 void RedPeer::shutdown()
231 if (_peer != INVALID_SOCKET) {
235 ::shutdown(_peer, SHUT_RDWR);
240 void RedPeer::disconnect()
246 void RedPeer::close()
249 if (_peer != INVALID_SOCKET) {
258 _peer = INVALID_SOCKET;
262 void RedPeer::swap(RedPeer* other)
265 SOCKET temp_peer = _peer;
266 SSL_CTX *temp_ctx = _ctx;
267 SSL *temp_ssl = _ssl;
269 _peer = other->_peer;
270 other->_peer = temp_peer;
276 other->_ctx = temp_ctx;
277 other->_ssl = temp_ssl;
285 uint32_t RedPeer::receive(uint8_t *buf, uint32_t size)
291 if ((now = recv(_peer, (char *)pos, size, 0)) <= 0) {
292 int err = sock_error();
293 if (now == SOCKET_ERROR && err == WOULDBLOCK_ERR) {
297 if (now == 0 || err == SHUTDOWN_ERR) {
298 throw RedPeer::DisconnectedException();
301 if (err == INTERRUPTED_ERR) {
304 THROW_ERR(SPICEC_ERROR_CODE_RECV_FAILED, "%s (%d)", sock_err_message(err), err);
309 if ((now = SSL_read(_ssl, pos, size)) <= 0) {
310 int ssl_error = SSL_get_error(_ssl, now);
312 if (ssl_error == SSL_ERROR_WANT_READ) {
316 if (ssl_error == SSL_ERROR_SYSCALL) {
317 int err = sock_error();
319 if (err == WOULDBLOCK_ERR) {
322 if (err == INTERRUPTED_ERR) {
326 if (now == 0 || (now == -1 && err == SHUTDOWN_ERR)) {
327 throw RedPeer::DisconnectedException();
329 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "%s (%d)", sock_err_message(err), err);
330 } else if (ssl_error == SSL_ERROR_ZERO_RETURN) {
331 throw RedPeer::DisconnectedException();
333 THROW_ERR(SPICEC_ERROR_CODE_RECV_FAILED, "ssl error %d", ssl_error);
342 RedPeer::CompoundInMessage* RedPeer::receive()
344 SpiceDataHeader header;
345 AutoRef<CompoundInMessage> message;
347 receive((uint8_t*)&header, sizeof(SpiceDataHeader));
348 message.reset(new CompoundInMessage(header.serial, header.type, header.size, header.sub_list));
349 receive((*message)->data(), (*message)->compound_size());
350 return message.release();
353 uint32_t RedPeer::send(uint8_t *buf, uint32_t size)
360 if ((now = ::send(_peer, (char *)pos, size, 0)) == SOCKET_ERROR) {
361 int err = sock_error();
362 if (err == WOULDBLOCK_ERR) {
365 if (err == SHUTDOWN_ERR) {
366 throw RedPeer::DisconnectedException();
368 if (err == INTERRUPTED_ERR) {
371 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "%s (%d)", sock_err_message(err), err);
376 if ((now = SSL_write(_ssl, pos, size)) <= 0) {
377 int ssl_error = SSL_get_error(_ssl, now);
379 if (ssl_error == SSL_ERROR_WANT_WRITE) {
382 if (ssl_error == SSL_ERROR_SYSCALL) {
383 int err = sock_error();
385 if (err == WOULDBLOCK_ERR) {
388 if (err == INTERRUPTED_ERR) {
392 if (now == 0 || (now == -1 && err == SHUTDOWN_ERR)) {
393 throw RedPeer::DisconnectedException();
395 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "%s (%d)", sock_err_message(err), err);
396 } else if (ssl_error == SSL_ERROR_ZERO_RETURN) {
397 throw RedPeer::DisconnectedException();
399 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "ssl error %d", ssl_error);
408 uint32_t RedPeer::do_send(RedPeer::OutMessage& message, uint32_t skip_bytes)
415 data = spice_marshaller_linearize(message.marshaller(), skip_bytes,
418 res = send(data, len);
426 uint32_t RedPeer::send(RedPeer::OutMessage& message)
429 message.header().serial = ++_serial;
430 message.header().size = message.message_size() - sizeof(SpiceDataHeader);
432 return do_send(message, 0);
435 RedPeer::OutMessage::OutMessage(uint32_t type)
436 : _marshaller (spice_marshaller_new())
438 SpiceDataHeader *header;
439 header = (SpiceDataHeader *)
440 spice_marshaller_reserve_space(_marshaller, sizeof(SpiceDataHeader));
441 spice_marshaller_set_base(_marshaller, sizeof(SpiceDataHeader));
444 header->sub_list = 0;
447 void RedPeer::OutMessage::reset(uint32_t type)
449 spice_marshaller_reset(_marshaller);
451 SpiceDataHeader *header;
452 header = (SpiceDataHeader *)
453 spice_marshaller_reserve_space(_marshaller, sizeof(SpiceDataHeader));
454 spice_marshaller_set_base(_marshaller, sizeof(SpiceDataHeader));
457 header->sub_list = 0;
460 RedPeer::OutMessage::~OutMessage()
462 spice_marshaller_destroy(_marshaller);