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