Line data Source code
1 : // Copyright (c) 2011-2014 The Bitcoin Core developers
2 : // Distributed under the MIT software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include "paymentserver.h"
6 :
7 : #include "bitcoinunits.h"
8 : #include "guiutil.h"
9 : #include "optionsmodel.h"
10 :
11 : #include "base58.h"
12 : #include "chainparams.h"
13 : #include "main.h" // For minRelayTxFee
14 : #include "ui_interface.h"
15 : #include "util.h"
16 : #include "wallet/wallet.h"
17 :
18 : #include <cstdlib>
19 :
20 : #include <openssl/x509_vfy.h>
21 :
22 : #include <QApplication>
23 : #include <QByteArray>
24 : #include <QDataStream>
25 : #include <QDateTime>
26 : #include <QDebug>
27 : #include <QFile>
28 : #include <QFileOpenEvent>
29 : #include <QHash>
30 : #include <QList>
31 : #include <QLocalServer>
32 : #include <QLocalSocket>
33 : #include <QNetworkAccessManager>
34 : #include <QNetworkProxy>
35 : #include <QNetworkReply>
36 : #include <QNetworkRequest>
37 : #include <QSslCertificate>
38 : #include <QSslError>
39 : #include <QSslSocket>
40 : #include <QStringList>
41 : #include <QTextDocument>
42 :
43 : #if QT_VERSION < 0x050000
44 : #include <QUrl>
45 : #else
46 : #include <QUrlQuery>
47 : #endif
48 :
49 : const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
50 1 : const QString BITCOIN_IPC_PREFIX("bitcoin:");
51 : // BIP70 payment protocol messages
52 : const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
53 : const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
54 : // BIP71 payment protocol media types
55 : const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment";
56 : const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
57 : const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
58 : // BIP70 max payment request size in bytes (DoS protection)
59 : const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000;
60 :
61 : X509_STORE* PaymentServer::certStore = NULL;
62 1 : void PaymentServer::freeCertStore()
63 : {
64 3 : if (PaymentServer::certStore != NULL)
65 : {
66 3 : X509_STORE_free(PaymentServer::certStore);
67 3 : PaymentServer::certStore = NULL;
68 : }
69 1 : }
70 :
71 : //
72 : // Create a name that is unique for:
73 : // testnet / non-testnet
74 : // data directory
75 : //
76 1 : static QString ipcServerName()
77 : {
78 : QString name("BitcoinQt");
79 :
80 : // Append a simple hash of the datadir
81 : // Note that GetDataDir(true) returns a different path
82 : // for -testnet versus main net
83 3 : QString ddir(QString::fromStdString(GetDataDir(true).string()));
84 1 : name.append(QString::number(qHash(ddir)));
85 :
86 1 : return name;
87 : }
88 :
89 : //
90 : // We store payment URIs and requests received before
91 : // the main GUI window is up and ready to ask the user
92 : // to send payment.
93 :
94 1 : static QList<QString> savedPaymentRequests;
95 :
96 0 : static void ReportInvalidCertificate(const QSslCertificate& cert)
97 : {
98 : #if QT_VERSION < 0x050000
99 0 : qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
100 : #else
101 : qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
102 : #endif
103 0 : }
104 :
105 : //
106 : // Load OpenSSL's list of root certificate authorities
107 : //
108 3 : void PaymentServer::LoadRootCAs(X509_STORE* _store)
109 : {
110 3 : if (PaymentServer::certStore == NULL)
111 1 : atexit(PaymentServer::freeCertStore);
112 : else
113 : freeCertStore();
114 :
115 : // Unit tests mostly use this, to pass in fake root CAs:
116 3 : if (_store)
117 : {
118 3 : PaymentServer::certStore = _store;
119 6 : return;
120 : }
121 :
122 : // Normal execution, use either -rootcertificates or system certs:
123 0 : PaymentServer::certStore = X509_STORE_new();
124 :
125 : // Note: use "-system-" default here so that users can pass -rootcertificates=""
126 : // and get 'I don't like X.509 certificates, don't trust anybody' behavior:
127 0 : QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-"));
128 :
129 : // Empty store
130 0 : if (certFile.isEmpty()) {
131 0 : qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__);
132 0 : return;
133 : }
134 :
135 0 : QList<QSslCertificate> certList;
136 :
137 0 : if (certFile != "-system-") {
138 0 : qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile);
139 :
140 0 : certList = QSslCertificate::fromPath(certFile);
141 : // Use those certificates when fetching payment requests, too:
142 0 : QSslSocket::setDefaultCaCertificates(certList);
143 : } else
144 0 : certList = QSslSocket::systemCaCertificates();
145 :
146 0 : int nRootCerts = 0;
147 0 : const QDateTime currentTime = QDateTime::currentDateTime();
148 :
149 0 : Q_FOREACH (const QSslCertificate& cert, certList) {
150 : // Don't log NULL certificates
151 0 : if (cert.isNull())
152 0 : continue;
153 :
154 : // Not yet active/valid, or expired certificate
155 0 : if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
156 0 : ReportInvalidCertificate(cert);
157 : continue;
158 : }
159 :
160 : #if QT_VERSION >= 0x050000
161 : // Blacklisted certificate
162 : if (cert.isBlacklisted()) {
163 : ReportInvalidCertificate(cert);
164 : continue;
165 : }
166 : #endif
167 0 : QByteArray certData = cert.toDer();
168 0 : const unsigned char *data = (const unsigned char *)certData.data();
169 :
170 0 : X509* x509 = d2i_X509(0, &data, certData.size());
171 0 : if (x509 && X509_STORE_add_cert(PaymentServer::certStore, x509))
172 : {
173 : // Note: X509_STORE_free will free the X509* objects when
174 : // the PaymentServer is destroyed
175 0 : ++nRootCerts;
176 : }
177 : else
178 : {
179 0 : ReportInvalidCertificate(cert);
180 0 : continue;
181 : }
182 0 : }
183 0 : qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates";
184 :
185 : // Project for another day:
186 : // Fetch certificate revocation lists, and add them to certStore.
187 : // Issues to consider:
188 : // performance (start a thread to fetch in background?)
189 : // privacy (fetch through tor/proxy so IP address isn't revealed)
190 : // would it be easier to just use a compiled-in blacklist?
191 : // or use Qt's blacklist?
192 : // "certificate stapling" with server-side caching is more efficient
193 : }
194 :
195 : //
196 : // Sending to the server is done synchronously, at startup.
197 : // If the server isn't already running, startup continues,
198 : // and the items in savedPaymentRequest will be handled
199 : // when uiReady() is called.
200 : //
201 : // Warning: ipcSendCommandLine() is called early in init,
202 : // so don't use "Q_EMIT message()", but "QMessageBox::"!
203 : //
204 0 : void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
205 : {
206 0 : for (int i = 1; i < argc; i++)
207 : {
208 0 : QString arg(argv[i]);
209 0 : if (arg.startsWith("-"))
210 0 : continue;
211 :
212 : // If the bitcoin: URI contains a payment request, we are not able to detect the
213 : // network as that would require fetching and parsing the payment request.
214 : // That means clicking such an URI which contains a testnet payment request
215 : // will start a mainnet instance and throw a "wrong network" error.
216 0 : if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
217 : {
218 0 : savedPaymentRequests.append(arg);
219 :
220 0 : SendCoinsRecipient r;
221 0 : if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
222 : {
223 0 : CBitcoinAddress address(r.address.toStdString());
224 :
225 0 : if (address.IsValid(Params(CBaseChainParams::MAIN)))
226 : {
227 0 : SelectParams(CBaseChainParams::MAIN);
228 : }
229 0 : else if (address.IsValid(Params(CBaseChainParams::TESTNET)))
230 : {
231 0 : SelectParams(CBaseChainParams::TESTNET);
232 : }
233 0 : }
234 : }
235 0 : else if (QFile::exists(arg)) // Filename
236 : {
237 0 : savedPaymentRequests.append(arg);
238 :
239 0 : PaymentRequestPlus request;
240 0 : if (readPaymentRequestFromFile(arg, request))
241 : {
242 0 : if (request.getDetails().network() == "main")
243 : {
244 0 : SelectParams(CBaseChainParams::MAIN);
245 : }
246 0 : else if (request.getDetails().network() == "test")
247 : {
248 0 : SelectParams(CBaseChainParams::TESTNET);
249 : }
250 0 : }
251 : }
252 : else
253 : {
254 : // Printing to debug.log is about the best we can do here, the
255 : // GUI hasn't started yet so we can't pop up a message box.
256 0 : qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg;
257 : }
258 0 : }
259 0 : }
260 :
261 : //
262 : // Sending to the server is done synchronously, at startup.
263 : // If the server isn't already running, startup continues,
264 : // and the items in savedPaymentRequest will be handled
265 : // when uiReady() is called.
266 : //
267 0 : bool PaymentServer::ipcSendCommandLine()
268 : {
269 0 : bool fResult = false;
270 0 : Q_FOREACH (const QString& r, savedPaymentRequests)
271 : {
272 0 : QLocalSocket* socket = new QLocalSocket();
273 0 : socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
274 0 : if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
275 : {
276 0 : delete socket;
277 0 : socket = NULL;
278 0 : return false;
279 : }
280 :
281 : QByteArray block;
282 0 : QDataStream out(&block, QIODevice::WriteOnly);
283 0 : out.setVersion(QDataStream::Qt_4_0);
284 0 : out << r;
285 0 : out.device()->seek(0);
286 :
287 0 : socket->write(block);
288 0 : socket->flush();
289 0 : socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
290 0 : socket->disconnectFromServer();
291 :
292 0 : delete socket;
293 0 : socket = NULL;
294 0 : fResult = true;
295 0 : }
296 :
297 0 : return fResult;
298 : }
299 :
300 1 : PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
301 : QObject(parent),
302 : saveURIs(true),
303 : uriServer(0),
304 : netManager(0),
305 1 : optionsModel(0)
306 : {
307 : // Verify that the version of the library that we linked against is
308 : // compatible with the version of the headers we compiled against.
309 1 : GOOGLE_PROTOBUF_VERIFY_VERSION;
310 :
311 : // Install global event filter to catch QFileOpenEvents
312 : // on Mac: sent when you click bitcoin: links
313 : // other OSes: helpful when dealing with payment request files
314 1 : if (parent)
315 0 : parent->installEventFilter(this);
316 :
317 1 : QString name = ipcServerName();
318 :
319 : // Clean up old socket leftover from a crash:
320 1 : QLocalServer::removeServer(name);
321 :
322 1 : if (startLocalServer)
323 : {
324 0 : uriServer = new QLocalServer(this);
325 :
326 0 : if (!uriServer->listen(name)) {
327 : // constructor is called early in init, so don't use "Q_EMIT message()" here
328 : QMessageBox::critical(0, tr("Payment request error"),
329 0 : tr("Cannot start bitcoin: click-to-pay handler"));
330 : }
331 : else {
332 0 : connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
333 0 : connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
334 : }
335 1 : }
336 1 : }
337 :
338 3 : PaymentServer::~PaymentServer()
339 : {
340 1 : google::protobuf::ShutdownProtobufLibrary();
341 2 : }
342 :
343 : //
344 : // OSX-specific way of handling bitcoin: URIs and PaymentRequest mime types.
345 : // Also used by paymentservertests.cpp and when opening a payment request file
346 : // via "Open URI..." menu entry.
347 : //
348 6 : bool PaymentServer::eventFilter(QObject *object, QEvent *event)
349 : {
350 12 : if (event->type() == QEvent::FileOpen) {
351 6 : QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
352 6 : if (!fileEvent->file().isEmpty())
353 6 : handleURIOrFile(fileEvent->file());
354 0 : else if (!fileEvent->url().isEmpty())
355 0 : handleURIOrFile(fileEvent->url().toString());
356 :
357 : return true;
358 : }
359 :
360 0 : return QObject::eventFilter(object, event);
361 : }
362 :
363 1 : void PaymentServer::initNetManager()
364 : {
365 1 : if (!optionsModel)
366 0 : return;
367 1 : if (netManager != NULL)
368 0 : delete netManager;
369 :
370 : // netManager is used to fetch paymentrequests given in bitcoin: URIs
371 1 : netManager = new QNetworkAccessManager(this);
372 :
373 1 : QNetworkProxy proxy;
374 :
375 : // Query active SOCKS5 proxy
376 1 : if (optionsModel->getProxySettings(proxy)) {
377 0 : netManager->setProxy(proxy);
378 :
379 0 : qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port();
380 : }
381 : else
382 1 : qDebug() << "PaymentServer::initNetManager: No active proxy server found.";
383 :
384 : connect(netManager, SIGNAL(finished(QNetworkReply*)),
385 1 : this, SLOT(netRequestFinished(QNetworkReply*)));
386 : connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
387 1 : this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
388 : }
389 :
390 1 : void PaymentServer::uiReady()
391 : {
392 1 : initNetManager();
393 :
394 1 : saveURIs = false;
395 2 : Q_FOREACH (const QString& s, savedPaymentRequests)
396 : {
397 0 : handleURIOrFile(s);
398 : }
399 1 : savedPaymentRequests.clear();
400 1 : }
401 :
402 6 : void PaymentServer::handleURIOrFile(const QString& s)
403 : {
404 6 : if (saveURIs)
405 : {
406 0 : savedPaymentRequests.append(s);
407 0 : return;
408 : }
409 :
410 6 : if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
411 : {
412 : #if QT_VERSION < 0x050000
413 0 : QUrl uri(s);
414 : #else
415 : QUrlQuery uri((QUrl(s)));
416 : #endif
417 0 : if (uri.hasQueryItem("r")) // payment request URI
418 : {
419 : QByteArray temp;
420 0 : temp.append(uri.queryItemValue("r"));
421 0 : QString decoded = QUrl::fromPercentEncoding(temp);
422 0 : QUrl fetchUrl(decoded, QUrl::StrictMode);
423 :
424 0 : if (fetchUrl.isValid())
425 : {
426 0 : qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")";
427 0 : fetchRequest(fetchUrl);
428 : }
429 : else
430 : {
431 0 : qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl;
432 : Q_EMIT message(tr("URI handling"),
433 : tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
434 0 : CClientUIInterface::ICON_WARNING);
435 : }
436 :
437 0 : return;
438 : }
439 : else // normal URI
440 : {
441 0 : SendCoinsRecipient recipient;
442 0 : if (GUIUtil::parseBitcoinURI(s, &recipient))
443 : {
444 0 : CBitcoinAddress address(recipient.address.toStdString());
445 0 : if (!address.IsValid()) {
446 : Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
447 0 : CClientUIInterface::MSG_ERROR);
448 : }
449 : else
450 0 : Q_EMIT receivedPaymentRequest(recipient);
451 : }
452 : else
453 : Q_EMIT message(tr("URI handling"),
454 : tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
455 0 : CClientUIInterface::ICON_WARNING);
456 :
457 0 : return;
458 0 : }
459 : }
460 :
461 6 : if (QFile::exists(s)) // payment request file
462 : {
463 6 : PaymentRequestPlus request;
464 12 : SendCoinsRecipient recipient;
465 6 : if (!readPaymentRequestFromFile(s, request))
466 : {
467 : Q_EMIT message(tr("Payment request file handling"),
468 : tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
469 0 : CClientUIInterface::ICON_WARNING);
470 : }
471 6 : else if (processPaymentRequest(request, recipient))
472 6 : Q_EMIT receivedPaymentRequest(recipient);
473 :
474 6 : return;
475 : }
476 : }
477 :
478 0 : void PaymentServer::handleURIConnection()
479 : {
480 0 : QLocalSocket *clientConnection = uriServer->nextPendingConnection();
481 :
482 0 : while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
483 0 : clientConnection->waitForReadyRead();
484 :
485 : connect(clientConnection, SIGNAL(disconnected()),
486 0 : clientConnection, SLOT(deleteLater()));
487 :
488 0 : QDataStream in(clientConnection);
489 0 : in.setVersion(QDataStream::Qt_4_0);
490 0 : if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
491 0 : return;
492 : }
493 0 : QString msg;
494 0 : in >> msg;
495 :
496 0 : handleURIOrFile(msg);
497 : }
498 :
499 : //
500 : // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
501 : // so don't use "Q_EMIT message()", but "QMessageBox::"!
502 : //
503 6 : bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
504 : {
505 6 : QFile f(filename);
506 6 : if (!f.open(QIODevice::ReadOnly)) {
507 0 : qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
508 0 : return false;
509 : }
510 :
511 : // BIP70 DoS protection
512 6 : if (!verifySize(f.size())) {
513 : return false;
514 : }
515 :
516 12 : QByteArray data = f.readAll();
517 :
518 12 : return request.parse(data);
519 : }
520 :
521 6 : bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient)
522 : {
523 6 : if (!optionsModel)
524 : return false;
525 :
526 6 : if (request.IsInitialized()) {
527 : // Payment request network matches client network?
528 6 : if (!verifyNetwork(request.getDetails())) {
529 : Q_EMIT message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
530 0 : CClientUIInterface::MSG_ERROR);
531 :
532 0 : return false;
533 : }
534 :
535 : // Make sure any payment requests involved are still valid.
536 : // This is re-checked just before sending coins in WalletModel::sendCoins().
537 6 : if (verifyExpired(request.getDetails())) {
538 : Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."),
539 0 : CClientUIInterface::MSG_ERROR);
540 :
541 0 : return false;
542 : }
543 : } else {
544 : Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."),
545 0 : CClientUIInterface::MSG_ERROR);
546 :
547 0 : return false;
548 : }
549 :
550 6 : recipient.paymentRequest = request;
551 6 : recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
552 :
553 6 : request.getMerchant(PaymentServer::certStore, recipient.authenticatedMerchant);
554 :
555 6 : QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo();
556 : QStringList addresses;
557 :
558 42 : Q_FOREACH(const PAIRTYPE(CScript, CAmount)& sendingTo, sendingTos) {
559 : // Extract and check destination addresses
560 : CTxDestination dest;
561 6 : if (ExtractDestination(sendingTo.first, dest)) {
562 : // Append destination address
563 24 : addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString()));
564 : }
565 0 : else if (!recipient.authenticatedMerchant.isEmpty()) {
566 : // Unauthenticated payment requests to custom bitcoin addresses are not supported
567 : // (there is no good way to tell the user where they are paying in a way they'd
568 : // have a chance of understanding).
569 : Q_EMIT message(tr("Payment request rejected"),
570 : tr("Unverified payment requests to custom payment scripts are unsupported."),
571 0 : CClientUIInterface::MSG_ERROR);
572 0 : return false;
573 : }
574 :
575 : // Bitcoin amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto),
576 : // but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range
577 : // and no overflow has happened.
578 6 : if (!verifyAmount(sendingTo.second)) {
579 0 : Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
580 0 : return false;
581 : }
582 :
583 : // Extract and check amounts
584 18 : CTxOut txOut(sendingTo.second, sendingTo.first);
585 6 : if (txOut.IsDust(::minRelayTxFee)) {
586 : Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
587 : .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
588 0 : CClientUIInterface::MSG_ERROR);
589 :
590 0 : return false;
591 : }
592 :
593 6 : recipient.amount += sendingTo.second;
594 : // Also verify that the final amount is still in a valid range after adding additional amounts.
595 6 : if (!verifyAmount(recipient.amount)) {
596 0 : Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
597 0 : return false;
598 : }
599 : }
600 : // Store addresses and format them to fit nicely into the GUI
601 6 : recipient.address = addresses.join("<br />");
602 :
603 12 : if (!recipient.authenticatedMerchant.isEmpty()) {
604 2 : qDebug() << "PaymentServer::processPaymentRequest: Secure payment request from " << recipient.authenticatedMerchant;
605 : }
606 : else {
607 4 : qDebug() << "PaymentServer::processPaymentRequest: Insecure payment request to " << addresses.join(", ");
608 : }
609 :
610 6 : return true;
611 : }
612 :
613 0 : void PaymentServer::fetchRequest(const QUrl& url)
614 : {
615 0 : QNetworkRequest netRequest;
616 0 : netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
617 0 : netRequest.setUrl(url);
618 0 : netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
619 0 : netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
620 0 : netManager->get(netRequest);
621 0 : }
622 :
623 0 : void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction)
624 : {
625 0 : const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
626 0 : if (!details.has_payment_url())
627 0 : return;
628 :
629 0 : QNetworkRequest netRequest;
630 0 : netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
631 0 : netRequest.setUrl(QString::fromStdString(details.payment_url()));
632 0 : netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
633 0 : netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
634 0 : netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
635 :
636 0 : payments::Payment payment;
637 0 : payment.set_merchant_data(details.merchant_data());
638 0 : payment.add_transactions(transaction.data(), transaction.size());
639 :
640 : // Create a new refund address, or re-use:
641 0 : QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
642 0 : std::string strAccount = account.toStdString();
643 0 : std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
644 0 : if (!refundAddresses.empty()) {
645 0 : CScript s = GetScriptForDestination(*refundAddresses.begin());
646 0 : payments::Output* refund_to = payment.add_refund_to();
647 0 : refund_to->set_script(&s[0], s.size());
648 : }
649 : else {
650 : CPubKey newKey;
651 0 : if (wallet->GetKeyFromPool(newKey)) {
652 0 : CKeyID keyID = newKey.GetID();
653 0 : wallet->SetAddressBook(keyID, strAccount, "refund");
654 :
655 0 : CScript s = GetScriptForDestination(keyID);
656 0 : payments::Output* refund_to = payment.add_refund_to();
657 0 : refund_to->set_script(&s[0], s.size());
658 : }
659 : else {
660 : // This should never happen, because sending coins should have
661 : // just unlocked the wallet and refilled the keypool.
662 0 : qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
663 : }
664 : }
665 :
666 0 : int length = payment.ByteSize();
667 0 : netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
668 0 : QByteArray serData(length, '\0');
669 0 : if (payment.SerializeToArray(serData.data(), length)) {
670 0 : netManager->post(netRequest, serData);
671 : }
672 : else {
673 : // This should never happen, either.
674 0 : qWarning() << "PaymentServer::fetchPaymentACK: Error serializing payment message";
675 0 : }
676 : }
677 :
678 0 : void PaymentServer::netRequestFinished(QNetworkReply* reply)
679 : {
680 0 : reply->deleteLater();
681 :
682 : // BIP70 DoS protection
683 0 : if (!verifySize(reply->size())) {
684 : Q_EMIT message(tr("Payment request rejected"),
685 : tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
686 : .arg(reply->request().url().toString())
687 0 : .arg(reply->size())
688 : .arg(BIP70_MAX_PAYMENTREQUEST_SIZE),
689 0 : CClientUIInterface::MSG_ERROR);
690 0 : return;
691 : }
692 :
693 0 : if (reply->error() != QNetworkReply::NoError) {
694 : QString msg = tr("Error communicating with %1: %2")
695 : .arg(reply->request().url().toString())
696 0 : .arg(reply->errorString());
697 :
698 0 : qWarning() << "PaymentServer::netRequestFinished: " << msg;
699 0 : Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
700 0 : return;
701 : }
702 :
703 0 : QByteArray data = reply->readAll();
704 :
705 0 : QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
706 0 : if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
707 : {
708 0 : PaymentRequestPlus request;
709 0 : SendCoinsRecipient recipient;
710 0 : if (!request.parse(data))
711 : {
712 0 : qWarning() << "PaymentServer::netRequestFinished: Error parsing payment request";
713 : Q_EMIT message(tr("Payment request error"),
714 : tr("Payment request cannot be parsed!"),
715 0 : CClientUIInterface::MSG_ERROR);
716 : }
717 0 : else if (processPaymentRequest(request, recipient))
718 0 : Q_EMIT receivedPaymentRequest(recipient);
719 :
720 0 : return;
721 : }
722 0 : else if (requestType == BIP70_MESSAGE_PAYMENTACK)
723 : {
724 0 : payments::PaymentACK paymentACK;
725 0 : if (!paymentACK.ParseFromArray(data.data(), data.size()))
726 : {
727 : QString msg = tr("Bad response from server %1")
728 0 : .arg(reply->request().url().toString());
729 :
730 0 : qWarning() << "PaymentServer::netRequestFinished: " << msg;
731 0 : Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
732 : }
733 : else
734 : {
735 0 : Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
736 0 : }
737 0 : }
738 : }
739 :
740 0 : void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
741 : {
742 : Q_UNUSED(reply);
743 :
744 : QString errString;
745 0 : Q_FOREACH (const QSslError& err, errs) {
746 0 : qWarning() << "PaymentServer::reportSslErrors: " << err;
747 0 : errString += err.errorString() + "\n";
748 : }
749 0 : Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR);
750 0 : }
751 :
752 1 : void PaymentServer::setOptionsModel(OptionsModel *optionsModel)
753 : {
754 1 : this->optionsModel = optionsModel;
755 1 : }
756 :
757 0 : void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
758 : {
759 : // currently we don't further process or store the paymentACK message
760 0 : Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
761 0 : }
762 :
763 7 : bool PaymentServer::verifyNetwork(const payments::PaymentDetails& requestDetails)
764 : {
765 21 : bool fVerified = requestDetails.network() == Params().NetworkIDString();
766 7 : if (!fVerified) {
767 : qWarning() << QString("PaymentServer::%1: Payment request network \"%2\" doesn't match client network \"%3\".")
768 : .arg(__func__)
769 1 : .arg(QString::fromStdString(requestDetails.network()))
770 4 : .arg(QString::fromStdString(Params().NetworkIDString()));
771 : }
772 7 : return fVerified;
773 : }
774 :
775 9 : bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails)
776 : {
777 18 : bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime());
778 9 : if (fVerified) {
779 4 : const QString requestExpires = QString::fromStdString(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires()));
780 : qWarning() << QString("PaymentServer::%1: Payment request expired \"%2\".")
781 : .arg(__func__)
782 4 : .arg(requestExpires);
783 : }
784 9 : return fVerified;
785 : }
786 :
787 7 : bool PaymentServer::verifySize(qint64 requestSize)
788 : {
789 7 : bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
790 7 : if (!fVerified) {
791 : qWarning() << QString("PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes).")
792 : .arg(__func__)
793 : .arg(requestSize)
794 2 : .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
795 : }
796 7 : return fVerified;
797 : }
798 :
799 13 : bool PaymentServer::verifyAmount(const CAmount& requestAmount)
800 : {
801 26 : bool fVerified = MoneyRange(requestAmount);
802 13 : if (!fVerified) {
803 : qWarning() << QString("PaymentServer::%1: Payment request amount out of allowed range (%2, allowed 0 - %3).")
804 : .arg(__func__)
805 : .arg(requestAmount)
806 3 : .arg(MAX_MONEY);
807 : }
808 13 : return fVerified;
809 3 : }
|