uploaded original spice-server-0.12.4 and celt-0.5.1.3
[sdk/emulator/libs/spice-server.git] / client / red_peer.cpp
1 /* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3    Copyright (C) 2009 Red Hat, Inc.
4
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.
9
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.
14
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/>.
17 */
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
21
22 #ifdef WIN32
23 #include <winsock2.h>
24 #endif
25 #include <openssl/x509.h>
26 #include <openssl/x509v3.h>
27 #include <spice/protocol.h>
28 #include "common/ssl_verify.h"
29
30 #include "common.h"
31 #include "red_peer.h"
32 #include "utils.h"
33 #include "debug.h"
34 #include "platform_utils.h"
35
36 static void SPICE_GNUC_NORETURN ssl_error()
37 {
38     unsigned long last_error = ERR_peek_last_error();
39
40     ERR_print_errors_fp(stderr);
41     THROW_ERR(SPICEC_ERROR_CODE_SSL_ERROR, "SSL Error: %s", ERR_error_string(last_error, NULL));
42 }
43
44 RedPeer::RedPeer()
45     : _peer (INVALID_SOCKET)
46     , _shut (false)
47     , _ctx (NULL)
48     , _ssl (NULL)
49 {
50 }
51
52 RedPeer::~RedPeer()
53 {
54     cleanup();
55 }
56
57 void RedPeer::cleanup()
58 {
59     if (_ssl) {
60         SSL_free(_ssl);
61         _ssl = NULL;
62     }
63
64     if (_ctx) {
65         SSL_CTX_free(_ctx);
66         _ctx = NULL;
67     }
68
69     if (_peer != INVALID_SOCKET) {
70         closesocket(_peer);
71         _peer = INVALID_SOCKET;
72     }
73 }
74
75 void RedPeer::connect_to_peer(const char* host, int portnr)
76 {
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);
82     try {
83         memset(&ai,0, sizeof(ai));
84         ai.ai_flags = AI_CANONNAME;
85 #ifdef AI_ADDRCONFIG
86         ai.ai_flags |= AI_ADDRCONFIG;
87 #endif
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);
92         if (rc != 0) {
93             THROW_ERR(SPICEC_ERROR_CODE_GETHOSTBYNAME_FAILED, "cannot resolve host address %s", host);
94         }
95         Lock lock(_lock);
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);
102             }
103             if (setsockopt(_peer, IPPROTO_TCP, TCP_NODELAY, (const char*)&no_delay, sizeof(no_delay)) ==
104                 SOCKET_ERROR) {
105                 LOG_WARN("set TCP_NODELAY failed");
106             }
107
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);
112
113             if (::connect(_peer, e->ai_addr, e->ai_addrlen) == SOCKET_ERROR) {
114                 err = sock_error();
115                 LOG_INFO("Connect failed: %s (%d)",
116                          sock_err_message(err), err);
117                 closesocket(_peer);
118                 _peer = INVALID_SOCKET;
119                 continue;
120             }
121             DBG(0, "Connected to %s %s", uaddr, uport);
122             break;
123         }
124         lock.unlock();
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);
129         }
130         _serial = 0;
131     } catch (...) {
132         Lock lock(_lock);
133         cleanup();
134         throw;
135     }
136 }
137
138 void RedPeer::connect_unsecure(const char* host, int portnr)
139 {
140     connect_to_peer(host, portnr);
141     ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
142     LOG_INFO("Connected to %s %d", host, portnr);
143 }
144
145 void RedPeer::connect_secure(const ConnectionOptions& options, const char* host)
146 {
147     int return_code;
148     SPICE_SSL_VERIFY_OP auth_flags;
149     SpiceOpenSSLVerify* verify = NULL;
150     int portnr = options.secure_port;
151
152     connect_to_peer(host, portnr);
153     ASSERT(_ctx == NULL && _ssl == NULL && _peer != INVALID_SOCKET);
154     LOG_INFO("Connected to %s %d", host, portnr);
155
156     try {
157 #if OPENSSL_VERSION_NUMBER >= 0x10000000L
158         const SSL_METHOD *ssl_method = TLSv1_method();
159 #else
160         SSL_METHOD *ssl_method = TLSv1_method();
161 #endif
162         _ctx = SSL_CTX_new(ssl_method);
163         if (_ctx == NULL) {
164             ssl_error();
165         }
166
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());
172
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;
179                 }
180                 else {
181                     LOG_ERROR("SSL_CTX_load_verify_locations failed CA_file=%s", CA_file.c_str());
182                     ssl_error();
183                 }
184             }
185         }
186
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());
190             ssl_error();
191         }
192
193         _ssl = SSL_new(_ctx);
194         if (!_ssl) {
195             THROW("create ssl failed");
196         }
197
198         verify = spice_openssl_verify_new(
199             _ssl, auth_flags,
200             host,
201             (char*)&options.host_auth.host_pubkey[0],
202             options.host_auth.host_pubkey.size(),
203             options.host_auth.host_subject.c_str());
204
205         BIO* sbio = BIO_new_socket(_peer, BIO_NOCLOSE);
206         if (!sbio) {
207             THROW("alloc new socket bio failed");
208         }
209
210         SSL_set_bio(_ssl, sbio, sbio);
211
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));
217             ssl_error();
218         }
219     } catch (...) {
220         Lock lock(_lock);
221         spice_openssl_verify_free(verify);
222         cleanup();
223         throw;
224     }
225
226     spice_openssl_verify_free(verify);
227 }
228
229 void RedPeer::shutdown()
230 {
231     if (_peer != INVALID_SOCKET) {
232         if (_ssl) {
233             SSL_shutdown(_ssl);
234         }
235         ::shutdown(_peer, SHUT_RDWR);
236     }
237     _shut = true;
238 }
239
240 void RedPeer::disconnect()
241 {
242     Lock lock(_lock);
243     shutdown();
244 }
245
246 void RedPeer::close()
247 {
248     Lock lock(_lock);
249     if (_peer != INVALID_SOCKET) {
250         if (_ctx) {
251             SSL_free(_ssl);
252             _ssl = NULL;
253             SSL_CTX_free(_ctx);
254             _ctx = NULL;
255         }
256
257         closesocket(_peer);
258         _peer = INVALID_SOCKET;
259     }
260 }
261
262 void RedPeer::swap(RedPeer* other)
263 {
264     Lock lock(_lock);
265     SOCKET temp_peer = _peer;
266     SSL_CTX *temp_ctx = _ctx;
267     SSL *temp_ssl = _ssl;
268
269     _peer = other->_peer;
270     other->_peer = temp_peer;
271
272     if (_ctx) {
273         _ctx = other->_ctx;
274         _ssl = other->_ssl;
275
276         other->_ctx = temp_ctx;
277         other->_ssl = temp_ssl;
278     }
279
280     if (_shut) {
281         shutdown();
282     }
283 }
284
285 uint32_t RedPeer::receive(uint8_t *buf, uint32_t size)
286 {
287     uint8_t *pos = buf;
288     while (size) {
289         int now;
290         if (_ctx == NULL) {
291             if ((now = recv(_peer, (char *)pos, size, 0)) <= 0) {
292                 int err = sock_error();
293                 if (now == SOCKET_ERROR && err == WOULDBLOCK_ERR) {
294                     break;
295                 }
296
297                 if (now == 0 || err == SHUTDOWN_ERR) {
298                     throw RedPeer::DisconnectedException();
299                 }
300
301                 if (err == INTERRUPTED_ERR) {
302                     continue;
303                 }
304                 THROW_ERR(SPICEC_ERROR_CODE_RECV_FAILED, "%s (%d)", sock_err_message(err), err);
305             }
306             size -= now;
307             pos += now;
308         } else {
309             if ((now = SSL_read(_ssl, pos, size)) <= 0) {
310                 int ssl_error = SSL_get_error(_ssl, now);
311
312                 if (ssl_error == SSL_ERROR_WANT_READ) {
313                     break;
314                 }
315
316                 if (ssl_error == SSL_ERROR_SYSCALL) {
317                     int err = sock_error();
318                     if (now == -1) {
319                         if (err == WOULDBLOCK_ERR) {
320                             break;
321                         }
322                         if (err == INTERRUPTED_ERR) {
323                             continue;
324                         }
325                     }
326                     if (now == 0 || (now == -1 && err == SHUTDOWN_ERR)) {
327                         throw RedPeer::DisconnectedException();
328                     }
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();
332                 }
333                 THROW_ERR(SPICEC_ERROR_CODE_RECV_FAILED, "ssl error %d", ssl_error);
334             }
335             size -= now;
336             pos += now;
337         }
338     }
339     return pos - buf;
340 }
341
342 RedPeer::CompoundInMessage* RedPeer::receive()
343 {
344     SpiceDataHeader header;
345     AutoRef<CompoundInMessage> message;
346
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();
351 }
352
353 uint32_t RedPeer::send(uint8_t *buf, uint32_t size)
354 {
355     uint8_t *pos = buf;
356     while (size) {
357         int now;
358
359         if (_ctx == NULL) {
360             if ((now = ::send(_peer, (char *)pos, size, 0)) == SOCKET_ERROR) {
361                 int err = sock_error();
362                 if (err == WOULDBLOCK_ERR) {
363                     break;
364                 }
365                 if (err == SHUTDOWN_ERR) {
366                     throw RedPeer::DisconnectedException();
367                 }
368                 if (err == INTERRUPTED_ERR) {
369                     continue;
370                 }
371                 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "%s (%d)", sock_err_message(err), err);
372             }
373             size -= now;
374             pos += now;
375         } else {
376             if ((now = SSL_write(_ssl, pos, size)) <= 0) {
377                 int ssl_error = SSL_get_error(_ssl, now);
378
379                 if (ssl_error == SSL_ERROR_WANT_WRITE) {
380                     break;
381                 }
382                 if (ssl_error == SSL_ERROR_SYSCALL) {
383                     int err = sock_error();
384                     if (now == -1) {
385                         if (err == WOULDBLOCK_ERR) {
386                             break;
387                         }
388                         if (err == INTERRUPTED_ERR) {
389                             continue;
390                         }
391                     }
392                     if (now == 0 || (now == -1 && err == SHUTDOWN_ERR)) {
393                         throw RedPeer::DisconnectedException();
394                     }
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();
398                 }
399                 THROW_ERR(SPICEC_ERROR_CODE_SEND_FAILED, "ssl error %d", ssl_error);
400             }
401             size -= now;
402             pos += now;
403         }
404     }
405     return pos - buf;
406 }
407
408 uint32_t RedPeer::do_send(RedPeer::OutMessage& message, uint32_t skip_bytes)
409 {
410     uint8_t *data;
411     int free_data;
412     size_t len;
413     uint32_t res;
414
415     data = spice_marshaller_linearize(message.marshaller(), skip_bytes,
416                                       &len, &free_data);
417
418     res = send(data, len);
419
420     if (free_data) {
421         free(data);
422     }
423     return res;
424 }
425
426 uint32_t RedPeer::send(RedPeer::OutMessage& message)
427 {
428
429     message.header().serial = ++_serial;
430     message.header().size = message.message_size() - sizeof(SpiceDataHeader);
431
432     return do_send(message, 0);
433 }
434
435 RedPeer::OutMessage::OutMessage(uint32_t type)
436     : _marshaller (spice_marshaller_new())
437 {
438     SpiceDataHeader *header;
439     header = (SpiceDataHeader *)
440         spice_marshaller_reserve_space(_marshaller, sizeof(SpiceDataHeader));
441     spice_marshaller_set_base(_marshaller, sizeof(SpiceDataHeader));
442
443     header->type = type;
444     header->sub_list = 0;
445 }
446
447 void RedPeer::OutMessage::reset(uint32_t type)
448 {
449     spice_marshaller_reset(_marshaller);
450
451     SpiceDataHeader *header;
452     header = (SpiceDataHeader *)
453         spice_marshaller_reserve_space(_marshaller, sizeof(SpiceDataHeader));
454     spice_marshaller_set_base(_marshaller, sizeof(SpiceDataHeader));
455
456     header->type = type;
457     header->sub_list = 0;
458 }
459
460 RedPeer::OutMessage::~OutMessage()
461 {
462     spice_marshaller_destroy(_marshaller);
463 }