]> granicus.if.org Git - icinga2/blob - lib/base/tlsstream.cpp
b8f46c771adcd4314a5b6d68284fb74329ed1b10
[icinga2] / lib / base / tlsstream.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/)        *
4  *                                                                            *
5  * This program is free software; you can redistribute it and/or              *
6  * modify it under the terms of the GNU General Public License                *
7  * as published by the Free Software Foundation; either version 2             *
8  * of the License, or (at your option) any later version.                     *
9  *                                                                            *
10  * This program 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              *
13  * GNU General Public License for more details.                               *
14  *                                                                            *
15  * You should have received a copy of the GNU General Public License          *
16  * along with this program; if not, write to the Free Software Foundation     *
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
18  ******************************************************************************/
19
20 #include "i2-base.h"
21
22 using namespace icinga;
23
24 int I2_EXPORT TlsStream::m_SSLIndex;
25 bool I2_EXPORT TlsStream::m_SSLIndexInitialized = false;
26
27 /**
28  * Constructor for the TlsStream class.
29  *
30  * @param role The role of the client.
31  * @param sslContext The SSL context for the client.
32  */
33 TlsStream::TlsStream(const Stream::Ptr& innerStream, TlsRole role, shared_ptr<SSL_CTX> sslContext)
34         : m_SSLContext(sslContext), m_SendQueue(boost::make_shared<FIFO>()), m_RecvQueue(boost::make_shared<FIFO>()),
35           m_InnerStream(innerStream), m_Role(role)
36 {
37         m_InnerStream->OnDataAvailable.connect(boost::bind(&TlsStream::DataAvailableHandler, this));
38         m_InnerStream->OnClosed.connect(boost::bind(&TlsStream::ClosedHandler, this));
39         m_SendQueue->Start();
40         m_RecvQueue->Start();
41 }
42
43 void TlsStream::Start(void)
44 {
45         m_SSL = shared_ptr<SSL>(SSL_new(m_SSLContext.get()), SSL_free);
46
47         m_SSLContext.reset();
48
49         if (!m_SSL)
50                 throw_exception(OpenSSLException("SSL_new failed", ERR_get_error()));
51
52         if (!GetClientCertificate())
53                 throw_exception(logic_error("No X509 client certificate was specified."));
54
55         if (!m_SSLIndexInitialized) {
56                 m_SSLIndex = SSL_get_ex_new_index(0, const_cast<char *>("TlsStream"), NULL, NULL, NULL);
57                 m_SSLIndexInitialized = true;
58         }
59
60         SSL_set_ex_data(m_SSL.get(), m_SSLIndex, this);
61
62         SSL_set_verify(m_SSL.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
63
64         m_BIO = BIO_new_I2Stream(m_InnerStream);
65         SSL_set_bio(m_SSL.get(), m_BIO, m_BIO);
66
67         if (m_Role == TlsRoleServer)
68                 SSL_set_accept_state(m_SSL.get());
69         else
70                 SSL_set_connect_state(m_SSL.get());
71
72         /*int rc = SSL_do_handshake(m_SSL.get());
73
74         if (rc == 1) {
75                 SetConnected(true);
76                 OnConnected(GetSelf());
77         }*/
78
79         Stream::Start();
80
81         HandleIO();
82 }
83
84 /**
85  * Retrieves the X509 certficate for this client.
86  *
87  * @returns The X509 certificate.
88  */
89 shared_ptr<X509> TlsStream::GetClientCertificate(void) const
90 {
91         return shared_ptr<X509>(SSL_get_certificate(m_SSL.get()), &Utility::NullDeleter);
92 }
93
94 /**
95  * Retrieves the X509 certficate for the peer.
96  *
97  * @returns The X509 certificate.
98  */
99 shared_ptr<X509> TlsStream::GetPeerCertificate(void) const
100 {
101         return shared_ptr<X509>(SSL_get_peer_certificate(m_SSL.get()), X509_free);
102 }
103
104 void TlsStream::DataAvailableHandler(void)
105 {
106         try {
107                 HandleIO();
108         } catch (...) {
109                 SetException(boost::current_exception());
110
111                 Close();
112         }
113 }
114
115 void TlsStream::ClosedHandler(void)
116 {
117         SetException(m_InnerStream->GetException());
118         Close();
119 }
120
121 /**
122  * Processes data for the stream.
123  */
124 void TlsStream::HandleIO(void)
125 {
126         char data[16 * 1024];
127         int rc;
128
129         if (!IsConnected()) {
130                 rc = SSL_do_handshake(m_SSL.get());
131
132                 if (rc == 1) {
133                         SetConnected(true);
134                 } else {
135                         switch (SSL_get_error(m_SSL.get(), rc)) {
136                                 case SSL_ERROR_WANT_WRITE:
137                                         /* fall through */
138                                 case SSL_ERROR_WANT_READ:
139                                         return;
140                                 case SSL_ERROR_ZERO_RETURN:
141                                         Close();
142                                         return;
143                                 default:
144                                         I2Stream_check_exception(m_BIO);
145                                         throw_exception(OpenSSLException("SSL_do_handshake failed", ERR_get_error()));
146                         }
147                 }
148         }
149
150         bool new_data = false, read_ok = true;
151
152         while (read_ok) {
153                 rc = SSL_read(m_SSL.get(), data, sizeof(data));
154
155                 if (rc > 0) {
156                         m_RecvQueue->Write(data, rc);
157                         new_data = true;
158                 } else {
159                         switch (SSL_get_error(m_SSL.get(), rc)) {
160                                 case SSL_ERROR_WANT_WRITE:
161                                         /* fall through */
162                                 case SSL_ERROR_WANT_READ:
163                                         read_ok = false;
164                                         break;
165                                 case SSL_ERROR_ZERO_RETURN:
166                                         Close();
167                                         return;
168                                 default:
169                                         I2Stream_check_exception(m_BIO);
170                                         throw_exception(OpenSSLException("SSL_read failed", ERR_get_error()));
171                         }
172                 }
173         }
174
175         if (new_data)
176                 OnDataAvailable(GetSelf());
177
178         while (m_SendQueue->GetAvailableBytes() > 0) {
179                 size_t count = m_SendQueue->GetAvailableBytes();
180
181                 if (count == 0)
182                         break;
183
184                 if (count > sizeof(data))
185                         count = sizeof(data);
186
187                 m_SendQueue->Peek(data, count);
188
189                 rc = SSL_write(m_SSL.get(), (const char *)data, count);
190
191                 if (rc > 0) {
192                         m_SendQueue->Read(NULL, rc);
193                 } else {
194                         switch (SSL_get_error(m_SSL.get(), rc)) {
195                                 case SSL_ERROR_WANT_READ:
196                                         /* fall through */
197                                 case SSL_ERROR_WANT_WRITE:
198                                         return;
199                                 case SSL_ERROR_ZERO_RETURN:
200                                         Close();
201                                         return;
202                                 default:
203                                         I2Stream_check_exception(m_BIO);
204                                         throw_exception(OpenSSLException("SSL_write failed", ERR_get_error()));
205                         }
206                 }
207         }
208 }
209
210 /**
211  * Closes the stream.
212  */
213 void TlsStream::Close(void)
214 {
215         if (m_SSL)
216                 SSL_shutdown(m_SSL.get());
217
218         m_SendQueue->Close();
219         m_RecvQueue->Close();
220
221         Stream::Close();
222 }
223
224 size_t TlsStream::GetAvailableBytes(void) const
225 {
226         return m_RecvQueue->GetAvailableBytes();
227 }
228
229 size_t TlsStream::Peek(void *buffer, size_t count)
230 {
231         return m_RecvQueue->Peek(buffer, count);
232 }
233
234 size_t TlsStream::Read(void *buffer, size_t count)
235 {
236         return m_RecvQueue->Read(buffer, count);
237 }
238
239 void TlsStream::Write(const void *buffer, size_t count)
240 {
241         m_SendQueue->Write(buffer, count);
242
243         HandleIO();
244 }