modernize: Replace most remaining old-style connects by PMF ones
[quassel.git] / src / core / sslserver.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
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                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "sslserver.h"
22
23 #ifdef HAVE_SSL
24 #  include <QSslSocket>
25 #endif
26
27 #include <QDateTime>
28
29 #include "quassel.h"
30 #include "logmessage.h"
31
32 #ifdef HAVE_SSL
33
34 SslServer::SslServer(QObject *parent)
35     : QTcpServer(parent)
36 {
37     // Keep track if the SSL warning has been mentioned at least once before
38     static bool sslWarningShown = false;
39
40     if(Quassel::isOptionSet("ssl-cert")) {
41         _sslCertPath = Quassel::optionValue("ssl-cert");
42     } else {
43         _sslCertPath = Quassel::configDirPath() + "quasselCert.pem";
44     }
45
46     if(Quassel::isOptionSet("ssl-key")) {
47         _sslKeyPath = Quassel::optionValue("ssl-key");
48     } else {
49         _sslKeyPath = _sslCertPath;
50     }
51
52     // Initialize the certificates for first-time usage
53     if (!loadCerts()) {
54         if (!sslWarningShown) {
55             quWarning()
56             << "SslServer: Unable to set certificate file\n"
57             << "          Quassel Core will still work, but cannot provide SSL for client connections.\n"
58             << "          Please see https://quassel-irc.org/faq/cert to learn how to enable SSL support.";
59             sslWarningShown = true;
60         }
61     }
62 }
63
64
65 QTcpSocket *SslServer::nextPendingConnection()
66 {
67     if (_pendingConnections.isEmpty())
68         return nullptr;
69     else
70         return _pendingConnections.takeFirst();
71 }
72
73
74 void SslServer::incomingConnection(qintptr socketDescriptor)
75 {
76     auto *serverSocket = new QSslSocket(this);
77     if (serverSocket->setSocketDescriptor(socketDescriptor)) {
78         if (isCertValid()) {
79             serverSocket->setLocalCertificate(_cert);
80             serverSocket->setPrivateKey(_key);
81             serverSocket->addCaCertificates(_ca);
82         }
83         _pendingConnections << serverSocket;
84         emit newConnection();
85     }
86     else {
87         delete serverSocket;
88     }
89 }
90
91
92 bool SslServer::loadCerts()
93 {
94     // Load the certificates specified in the path.  If needed, other prep work can be done here.
95     return setCertificate(_sslCertPath, _sslKeyPath);
96 }
97
98
99 bool SslServer::reloadCerts()
100 {
101     if (loadCerts()) {
102         return true;
103     } else {
104         // Reloading certificates currently only occur in response to a request.  Always print an
105         // error if something goes wrong, in order to simplify checking if it's working.
106         if (isCertValid()) {
107             quWarning()
108             << "SslServer: Unable to reload certificate file, reverting\n"
109             << "          Quassel Core will use the previous key to provide SSL for client connections.\n"
110             << "          Please see https://quassel-irc.org/faq/cert to learn how to enable SSL support.";
111         } else {
112             quWarning()
113             << "SslServer: Unable to reload certificate file\n"
114             << "          Quassel Core will still work, but cannot provide SSL for client connections.\n"
115             << "          Please see https://quassel-irc.org/faq/cert to learn how to enable SSL support.";
116         }
117         return false;
118     }
119 }
120
121
122 bool SslServer::setCertificate(const QString &path, const QString &keyPath)
123 {
124     // Don't reset _isCertValid here, in case an older but valid certificate is still loaded.
125     // Use temporary variables in order to avoid overwriting the existing certificates until
126     // everything is confirmed good.
127     QSslCertificate untestedCert;
128     QList<QSslCertificate> untestedCA;
129     QSslKey untestedKey;
130
131     if (path.isEmpty())
132         return false;
133
134     QFile certFile(path);
135     if (!certFile.exists()) {
136         quWarning() << "SslServer: Certificate file" << qPrintable(path) << "does not exist";
137         return false;
138     }
139
140     if (!certFile.open(QIODevice::ReadOnly)) {
141         quWarning()
142         << "SslServer: Failed to open certificate file" << qPrintable(path)
143         << "error:" << certFile.error();
144         return false;
145     }
146
147     QList<QSslCertificate> certList = QSslCertificate::fromDevice(&certFile);
148
149     if (certList.isEmpty()) {
150         quWarning() << "SslServer: Certificate file doesn't contain a certificate";
151         return false;
152     }
153
154     untestedCert = certList[0];
155     certList.removeFirst(); // remove server cert
156
157     // store CA and intermediates certs
158     untestedCA = certList;
159
160     if (!certFile.reset()) {
161         quWarning() << "SslServer: IO error reading certificate file";
162         return false;
163     }
164
165     // load key from keyPath if it differs from path, otherwise load key from path
166     if(path != keyPath) {
167         QFile keyFile(keyPath);
168         if(!keyFile.exists()) {
169             quWarning() << "SslServer: Key file" << qPrintable(keyPath) << "does not exist";
170             return false;
171         }
172
173         if (!keyFile.open(QIODevice::ReadOnly)) {
174             quWarning()
175             << "SslServer: Failed to open key file" << qPrintable(keyPath)
176             << "error:" << keyFile.error();
177             return false;
178         }
179
180         untestedKey = loadKey(&keyFile);
181         keyFile.close();
182     } else {
183         untestedKey = loadKey(&certFile);
184     }
185
186     certFile.close();
187
188     if (untestedCert.isNull()) {
189         quWarning() << "SslServer:" << qPrintable(path) << "contains no certificate data";
190         return false;
191     }
192
193     // We allow the core to offer SSL anyway, so no "return false" here. Client will warn about the cert being invalid.
194     const QDateTime now = QDateTime::currentDateTime();
195     if (now < untestedCert.effectiveDate()) {
196         quWarning() << "SslServer: Certificate won't be valid before" << untestedCert.effectiveDate().toString();
197     }
198     else if (now > untestedCert.expiryDate()) {
199         quWarning() << "SslServer: Certificate expired on" << untestedCert.expiryDate().toString();
200     }
201     else if (untestedCert.isBlacklisted()) {
202         quWarning() << "SslServer: Certificate blacklisted";
203     }
204
205     if (untestedKey.isNull()) {
206         quWarning() << "SslServer:" << qPrintable(keyPath) << "contains no key data";
207         return false;
208     }
209
210     _isCertValid = true;
211
212     // All keys are valid, update the externally visible copy used for new connections.
213     _cert = untestedCert;
214     _ca = untestedCA;
215     _key = untestedKey;
216
217     return _isCertValid;
218 }
219
220
221 QSslKey SslServer::loadKey(QFile *keyFile)
222 {
223     QSslKey key;
224     key = QSslKey(keyFile, QSsl::Rsa);
225     if (key.isNull()) {
226         if (!keyFile->reset()) {
227             quWarning() << "SslServer: IO error reading key file";
228             return key;
229         }
230         key = QSslKey(keyFile, QSsl::Ec);
231     }
232     return key;
233 }
234
235
236 #endif // HAVE_SSL