Initial commit of mtgonline project

This commit is contained in:
2026-07-18 04:57:40 +00:00
commit 86c12376f8
1870 changed files with 547994 additions and 0 deletions
+61
View File
@@ -0,0 +1,61 @@
#include "email_parser.h"
#include <QRegularExpression>
#include <QString>
QPair<QString, QString> EmailParser::parseEmailAddress(const QString &dirtyEmailAddress)
{
// https://www.regular-expressions.info/email.html
static const QRegularExpression emailRegex(R"(^([A-Z0-9._%+-]+)@([A-Z0-9.-]+\.[A-Z]{2,})$)",
QRegularExpression::CaseInsensitiveOption);
const auto match = emailRegex.match(dirtyEmailAddress);
if (dirtyEmailAddress.isEmpty() || !match.hasMatch()) {
return {};
}
QString capturedEmailUser = match.captured(1);
QString capturedEmailAddressDomain = match.captured(2);
// Replace googlemail.com with gmail.com, as is standard nowadays
// https://www.gmass.co/blog/domains-gmail-com-googlemail-com-and-google-com/
if (capturedEmailAddressDomain.toLower() == "googlemail.com") {
capturedEmailAddressDomain = "gmail.com";
}
// Trim out dots and pluses from Google/Gmail domains
if (capturedEmailAddressDomain.toLower() == "gmail.com") {
// Remove all content after the first plus sign (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
const auto firstPlusSign = capturedEmailUser.indexOf("+");
if (firstPlusSign != -1) {
capturedEmailUser = capturedEmailUser.left(firstPlusSign);
}
// Remove all periods (as unnecessary with gmail)
// https://gmail.googleblog.com/2008/03/2-hidden-ways-to-get-more-from-your.html
capturedEmailUser.replace(".", "");
}
// Trim out minuses from Yahoo domains
else if (capturedEmailAddressDomain.toLower() == "yahoo.com") {
const auto firstMinusSign = capturedEmailUser.indexOf("-");
if (firstMinusSign != -1) {
capturedEmailUser = capturedEmailUser.left(firstMinusSign);
}
}
return {capturedEmailUser, capturedEmailAddressDomain};
}
QString EmailParser::getParsedEmailAddress(const QString &dirtyEmailAddress)
{
const auto parsedEmailAddress = EmailParser::parseEmailAddress(dirtyEmailAddress);
return EmailParser::getParsedEmailAddress(parsedEmailAddress);
}
QString EmailParser::getParsedEmailAddress(const QPair<QString, QString> &emailAddressIntermediate)
{
const auto emailUser = emailAddressIntermediate.first;
const auto emailDomain = emailAddressIntermediate.second;
return emailUser + "@" + emailDomain;
}
+15
View File
@@ -0,0 +1,15 @@
#ifndef COCKATRICE_EMAILPARSER_H
#define COCKATRICE_EMAILPARSER_H
#include <QPair>
#include <QString>
class EmailParser
{
public:
static QPair<QString, QString> parseEmailAddress(const QString &dirtyEmailAddress);
static QString getParsedEmailAddress(const QString &dirtyEmailAddress);
static QString getParsedEmailAddress(const QPair<QString, QString> &emailAddressIntermediate);
};
#endif // COCKATRICE_EMAILPARSER_H
+482
View File
@@ -0,0 +1,482 @@
#include "isl_interface.h"
#include "main.h"
#include "server_logger.h"
#include <QLoggingCategory>
#include <QSslSocket>
#include <google/protobuf/descriptor.h>
#include <libcockatrice/protocol/debug_pb_message.h>
#include <libcockatrice/protocol/get_pb_extension.h>
#include <libcockatrice/protocol/pb/event_game_joined.pb.h>
#include <libcockatrice/protocol/pb/event_join_room.pb.h>
#include <libcockatrice/protocol/pb/event_leave_room.pb.h>
#include <libcockatrice/protocol/pb/event_list_games.pb.h>
#include <libcockatrice/protocol/pb/event_remove_messages.pb.h>
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
#include <libcockatrice/protocol/pb/event_server_complete_list.pb.h>
#include <libcockatrice/protocol/pb/event_user_joined.pb.h>
#include <libcockatrice/protocol/pb/event_user_left.pb.h>
#include <libcockatrice/protocol/pb/event_user_message.pb.h>
#include <libcockatrice/protocol/pb/isl_message.pb.h>
#include <server_protocolhandler.h>
#include <server_room.h>
inline Q_LOGGING_CATEGORY(IslInterfaceLog, "isl_interface");
void IslInterface::sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey)
{
socket = new QSslSocket(this);
socket->setLocalCertificate(cert);
socket->setPrivateKey(privateKey);
connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()), Qt::QueuedConnection);
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this,
SLOT(catchSocketError(QAbstractSocket::SocketError)));
connect(this, SIGNAL(outputBufferChanged()), this, SLOT(flushOutputBuffer()), Qt::QueuedConnection);
}
IslInterface::IslInterface(int _socketDescriptor,
const QSslCertificate &cert,
const QSslKey &privateKey,
Servatrice *_server)
: QObject(), socketDescriptor(_socketDescriptor), server(_server), messageInProgress(false)
{
sharedCtor(cert, privateKey);
}
IslInterface::IslInterface(int _serverId,
const QString &_peerHostName,
const QString &_peerAddress,
int _peerPort,
const QSslCertificate &_peerCert,
const QSslCertificate &cert,
const QSslKey &privateKey,
Servatrice *_server)
: QObject(), serverId(_serverId), peerHostName(_peerHostName), peerAddress(_peerAddress), peerPort(_peerPort),
peerCert(_peerCert), server(_server), messageInProgress(false)
{
sharedCtor(cert, privateKey);
}
IslInterface::~IslInterface()
{
logger->logMessage("[ISL] session ended", this);
flushOutputBuffer();
// As these signals are connected with Qt::QueuedConnection implicitly,
// we don't need to worry about them modifying the lists while we're iterating.
server->roomsLock.lockForRead();
QMapIterator<int, Server_Room *> roomIterator(server->getRooms());
while (roomIterator.hasNext()) {
Server_Room *room = roomIterator.next().value();
room->usersLock.lockForRead();
QMapIterator<QString, ServerInfo_User_Container> roomUsers(room->getExternalUsers());
while (roomUsers.hasNext()) {
roomUsers.next();
if (roomUsers.value().getUserInfo()->server_id() == serverId) {
emit externalRoomUserLeft(room->getId(), roomUsers.key());
}
}
room->usersLock.unlock();
}
server->roomsLock.unlock();
server->clientsLock.lockForRead();
QMapIterator<QString, Server_AbstractUserInterface *> extUsers(server->getExternalUsers());
while (extUsers.hasNext()) {
extUsers.next();
if (extUsers.value()->getUserInfo()->server_id() == serverId) {
emit externalUserLeft(extUsers.key());
}
}
server->clientsLock.unlock();
}
void IslInterface::initServer()
{
socket->setSocketDescriptor(socketDescriptor);
logger->logMessage(QString("[ISL] incoming connection: %1").arg(socket->peerAddress().toString()));
QList<ServerProperties> serverList = server->getServerList();
int listIndex = -1;
for (int i = 0; i < serverList.size(); ++i) {
if (serverList[i].address == socket->peerAddress()) {
listIndex = i;
break;
}
}
if (listIndex == -1) {
logger->logMessage(
QString("[ISL] address %1 unknown, terminating connection").arg(socket->peerAddress().toString()));
deleteLater();
return;
}
socket->startServerEncryption();
if (!socket->waitForEncrypted(5000)) {
QList<QSslError> sslErrors(socket->sslHandshakeErrors());
if (sslErrors.isEmpty()) {
qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection";
} else {
qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors;
}
deleteLater();
return;
}
if (serverList[listIndex].cert == socket->peerCertificate()) {
logger->logMessage(QString("[ISL] Peer authenticated as " + serverList[listIndex].hostname));
} else {
logger->logMessage(QString("[ISL] Authentication failed, terminating connection"));
deleteLater();
return;
}
serverId = serverList[listIndex].id;
Event_ServerCompleteList event;
event.set_server_id(server->getServerID());
server->clientsLock.lockForRead();
QMapIterator<QString, Server_ProtocolHandler *> userIterator(server->getUsers());
while (userIterator.hasNext()) {
event.add_user_list()->CopyFrom(userIterator.next().value()->copyUserInfo(true, true));
}
server->clientsLock.unlock();
server->roomsLock.lockForRead();
QMapIterator<int, Server_Room *> roomIterator(server->getRooms());
while (roomIterator.hasNext()) {
Server_Room *room = roomIterator.next().value();
room->usersLock.lockForRead();
room->gamesLock.lockForRead();
room->getInfo(*event.add_room_list(), true, true, false);
}
IslMessage message;
message.set_message_type(IslMessage::SESSION_EVENT);
SessionEvent *sessionEvent = message.mutable_session_event();
sessionEvent->GetReflection()
->MutableMessage(sessionEvent, event.GetDescriptor()->FindExtensionByName("ext"))
->CopyFrom(event);
server->islLock.lockForWrite();
if (server->islConnectionExists(serverId)) {
qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection";
deleteLater();
} else {
transmitMessage(message);
server->addIslInterface(serverId, this);
}
server->islLock.unlock();
roomIterator.toFront();
while (roomIterator.hasNext()) {
roomIterator.next();
roomIterator.value()->gamesLock.unlock();
roomIterator.value()->usersLock.unlock();
}
server->roomsLock.unlock();
}
void IslInterface::initClient()
{
QList<QSslError> expectedErrors;
expectedErrors.append(QSslError(QSslError::SelfSignedCertificate, peerCert));
socket->ignoreSslErrors(expectedErrors);
qCDebug(IslInterfaceLog) << "Connecting to #" << serverId << ":" << peerAddress << ":" << peerPort;
socket->connectToHostEncrypted(peerAddress, peerPort, peerHostName);
if (!socket->waitForConnected(5000)) {
qCDebug(IslInterfaceLog) << "Socket error:" << socket->errorString();
deleteLater();
return;
}
if (!socket->waitForEncrypted(5000)) {
QList<QSslError> sslErrors(socket->sslHandshakeErrors());
if (sslErrors.isEmpty()) {
qCDebug(IslInterfaceLog) << "SSL handshake timeout, terminating connection";
} else {
qCWarning(IslInterfaceLog) << "SSL errors:" << sslErrors;
}
deleteLater();
return;
}
server->islLock.lockForWrite();
if (server->islConnectionExists(serverId)) {
qCDebug(IslInterfaceLog) << "Duplicate connection to #" << serverId << "terminating connection";
deleteLater();
return;
}
server->addIslInterface(serverId, this);
server->islLock.unlock();
}
void IslInterface::flushOutputBuffer()
{
QMutexLocker locker(&outputBufferMutex);
if (outputBuffer.isEmpty()) {
return;
}
server->incTxBytes(outputBuffer.size());
socket->write(outputBuffer);
socket->flush();
outputBuffer.clear();
}
void IslInterface::readClient()
{
QByteArray data = socket->readAll();
server->incRxBytes(data.size());
inputBuffer.append(data);
do {
if (!messageInProgress) {
if (inputBuffer.size() >= 4) {
messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) +
(((quint32)(unsigned char)inputBuffer[1]) << 16) +
(((quint32)(unsigned char)inputBuffer[2]) << 8) +
((quint32)(unsigned char)inputBuffer[3]);
inputBuffer.remove(0, 4);
messageInProgress = true;
} else {
return;
}
}
if (inputBuffer.size() < messageLength) {
return;
}
IslMessage newMessage;
bool ok = newMessage.ParseFromArray(inputBuffer.data(), messageLength);
inputBuffer.remove(0, messageLength);
messageInProgress = false;
if (ok) {
processMessage(newMessage);
} else {
qCWarning(IslInterfaceLog) << "parsing error!";
}
} while (!inputBuffer.isEmpty());
}
void IslInterface::catchSocketError(QAbstractSocket::SocketError socketError)
{
qCWarning(IslInterfaceLog) << "Socket error:" << socketError;
server->islLock.lockForWrite();
server->removeIslInterface(serverId);
server->islLock.unlock();
deleteLater();
}
void IslInterface::transmitMessage(const IslMessage &item)
{
QByteArray buf;
#if GOOGLE_PROTOBUF_VERSION > 3001000
unsigned int size = static_cast<unsigned int>(item.ByteSizeLong());
#else
unsigned int size = static_cast<unsigned int>(item.ByteSize());
#endif
buf.resize(size + 4);
if (!item.SerializeToArray(buf.data() + 4, size)) {
qCWarning(IslInterfaceLog) << "transmit error!";
return;
}
buf.data()[3] = (unsigned char)size;
buf.data()[2] = (unsigned char)(size >> 8);
buf.data()[1] = (unsigned char)(size >> 16);
buf.data()[0] = (unsigned char)(size >> 24);
outputBufferMutex.lock();
outputBuffer.append(buf);
outputBufferMutex.unlock();
emit outputBufferChanged();
}
void IslInterface::sessionEvent_ServerCompleteList(const Event_ServerCompleteList &event)
{
for (int i = 0; i < event.user_list_size(); ++i) {
ServerInfo_User temp(event.user_list(i));
temp.set_server_id(serverId);
emit externalUserJoined(temp);
}
for (int i = 0; i < event.room_list_size(); ++i) {
const ServerInfo_Room &room = event.room_list(i);
for (int j = 0; j < room.user_list_size(); ++j) {
ServerInfo_User userInfo(room.user_list(j));
userInfo.set_server_id(serverId);
emit externalRoomUserJoined(room.room_id(), userInfo);
}
for (int j = 0; j < room.game_list_size(); ++j) {
ServerInfo_Game gameInfo(room.game_list(j));
gameInfo.set_server_id(serverId);
emit externalRoomGameListChanged(room.room_id(), gameInfo);
}
}
}
void IslInterface::sessionEvent_UserJoined(const Event_UserJoined &event)
{
ServerInfo_User userInfo(event.user_info());
userInfo.set_server_id(serverId);
emit externalUserJoined(userInfo);
}
void IslInterface::sessionEvent_UserLeft(const Event_UserLeft &event)
{
emit externalUserLeft(QString::fromStdString(event.name()));
}
void IslInterface::roomEvent_UserJoined(int roomId, const Event_JoinRoom &event)
{
ServerInfo_User userInfo(event.user_info());
userInfo.set_server_id(serverId);
emit externalRoomUserJoined(roomId, userInfo);
}
void IslInterface::roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event)
{
emit externalRoomUserLeft(roomId, QString::fromStdString(event.name()));
}
void IslInterface::roomEvent_Say(int roomId, const Event_RoomSay &event)
{
emit externalRoomSay(roomId, QString::fromStdString(event.name()), QString::fromStdString(event.message()));
}
void IslInterface::roomEvent_ListGames(int roomId, const Event_ListGames &event)
{
for (int i = 0; i < event.game_list_size(); ++i) {
ServerInfo_Game gameInfo(event.game_list(i));
gameInfo.set_server_id(serverId);
emit externalRoomGameListChanged(roomId, gameInfo);
}
}
void IslInterface::roomEvent_RemoveMessages(int roomId, const Event_RemoveMessages &event)
{
emit externalRoomRemoveMessages(roomId, QString::fromStdString(event.name()), event.amount());
}
void IslInterface::roomCommand_JoinGame(const Command_JoinGame &cmd, int cmdId, int roomId, qint64 sessionId)
{
emit joinGameCommandReceived(cmd, cmdId, roomId, serverId, sessionId);
}
void IslInterface::processSessionEvent(const SessionEvent &event, qint64 sessionId)
{
switch (getPbExtension(event)) {
case SessionEvent::SERVER_COMPLETE_LIST:
sessionEvent_ServerCompleteList(event.GetExtension(Event_ServerCompleteList::ext));
break;
case SessionEvent::USER_JOINED:
sessionEvent_UserJoined(event.GetExtension(Event_UserJoined::ext));
break;
case SessionEvent::USER_LEFT:
sessionEvent_UserLeft(event.GetExtension(Event_UserLeft::ext));
break;
case SessionEvent::GAME_JOINED: {
QReadLocker clientsLocker(&server->clientsLock);
Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId);
if (!client) {
qCDebug(IslInterfaceLog) << "IslInterface::processSessionEvent: session id" << sessionId << "not found";
break;
}
const Event_GameJoined &gameJoined = event.GetExtension(Event_GameJoined::ext);
client->playerAddedToGame(gameJoined.game_info().game_id(), gameJoined.game_info().room_id(),
gameJoined.player_id());
client->sendProtocolItem(event);
break;
}
case SessionEvent::USER_MESSAGE:
case SessionEvent::REPLAY_ADDED: {
QReadLocker clientsLocker(&server->clientsLock);
Server_AbstractUserInterface *client = server->getUsersBySessionId().value(sessionId);
if (!client) {
qCWarning(IslInterfaceLog)
<< "IslInterface::processSessionEvent: session id" << sessionId << "not found";
break;
}
client->sendProtocolItem(event);
break;
}
default:;
}
}
void IslInterface::processRoomEvent(const RoomEvent &event)
{
switch (getPbExtension(event)) {
case RoomEvent::JOIN_ROOM:
roomEvent_UserJoined(event.room_id(), event.GetExtension(Event_JoinRoom::ext));
break;
case RoomEvent::LEAVE_ROOM:
roomEvent_UserLeft(event.room_id(), event.GetExtension(Event_LeaveRoom::ext));
break;
case RoomEvent::ROOM_SAY:
roomEvent_Say(event.room_id(), event.GetExtension(Event_RoomSay::ext));
break;
case RoomEvent::LIST_GAMES:
roomEvent_ListGames(event.room_id(), event.GetExtension(Event_ListGames::ext));
break;
case RoomEvent::REMOVE_MESSAGES:
roomEvent_RemoveMessages(event.room_id(), event.GetExtension(Event_RemoveMessages::ext));
break;
default:;
}
}
void IslInterface::processRoomCommand(const CommandContainer &cont, qint64 sessionId)
{
for (int i = 0; i < cont.room_command_size(); ++i) {
const RoomCommand &roomCommand = cont.room_command(i);
switch (static_cast<RoomCommand::RoomCommandType>(getPbExtension(roomCommand))) {
case RoomCommand::JOIN_GAME:
roomCommand_JoinGame(roomCommand.GetExtension(Command_JoinGame::ext), cont.cmd_id(), cont.room_id(),
sessionId);
default:;
}
}
}
void IslInterface::processMessage(const IslMessage &item)
{
qCDebug(IslInterfaceLog) << getSafeDebugString(item);
switch (item.message_type()) {
case IslMessage::ROOM_COMMAND_CONTAINER: {
processRoomCommand(item.room_command(), item.session_id());
break;
}
case IslMessage::GAME_COMMAND_CONTAINER: {
emit gameCommandContainerReceived(item.game_command(), item.player_id(), serverId, item.session_id());
break;
}
case IslMessage::SESSION_EVENT: {
processSessionEvent(item.session_event(), item.session_id());
break;
}
case IslMessage::RESPONSE: {
emit responseReceived(item.response(), item.session_id());
break;
}
case IslMessage::GAME_EVENT_CONTAINER: {
emit gameEventContainerReceived(item.game_event_container(), item.session_id());
break;
}
case IslMessage::ROOM_EVENT: {
processRoomEvent(item.room_event());
break;
}
default:;
}
}
+102
View File
@@ -0,0 +1,102 @@
#ifndef ISL_INTERFACE_H
#define ISL_INTERFACE_H
#include "servatrice.h"
#include <QSslCertificate>
#include <QWaitCondition>
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
class Servatrice;
class QSslSocket;
class QSslKey;
class IslMessage;
class Event_ServerCompleteList;
class Event_UserMessage;
class Event_UserJoined;
class Event_UserLeft;
class Event_JoinRoom;
class Event_LeaveRoom;
class Event_RoomSay;
class Event_ListGames;
class Event_RemoveMessages;
class Command_JoinGame;
class IslInterface : public QObject
{
Q_OBJECT
private slots:
void readClient();
void catchSocketError(QAbstractSocket::SocketError socketError);
void flushOutputBuffer();
signals:
void outputBufferChanged();
void externalUserJoined(ServerInfo_User userInfo);
void externalUserLeft(QString userName);
void externalRoomUserJoined(int roomId, ServerInfo_User userInfo);
void externalRoomUserLeft(int roomId, QString userName);
void externalRoomSay(int roomId, QString userName, QString message);
void externalRoomGameListChanged(int roomId, ServerInfo_Game gameInfo);
void externalRoomRemoveMessages(int roomId, QString userName, int amount);
void joinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId);
void gameCommandContainerReceived(const CommandContainer &cont, int playerId, int serverId, qint64 sessionId);
void responseReceived(const Response &resp, qint64 sessionId);
void gameEventContainerReceived(const GameEventContainer &cont, qint64 sessionId);
private:
int serverId;
int socketDescriptor;
QString peerHostName, peerAddress;
int peerPort;
QSslCertificate peerCert;
QMutex outputBufferMutex;
Servatrice *server;
QSslSocket *socket;
QByteArray inputBuffer, outputBuffer;
bool messageInProgress;
int messageLength;
void sessionEvent_ServerCompleteList(const Event_ServerCompleteList &event);
void sessionEvent_UserJoined(const Event_UserJoined &event);
void sessionEvent_UserLeft(const Event_UserLeft &event);
void roomEvent_UserJoined(int roomId, const Event_JoinRoom &event);
void roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event);
void roomEvent_Say(int roomId, const Event_RoomSay &event);
void roomEvent_ListGames(int roomId, const Event_ListGames &event);
void roomEvent_RemoveMessages(int roomId, const Event_RemoveMessages &event);
void roomCommand_JoinGame(const Command_JoinGame &cmd, int cmdId, int roomId, qint64 sessionId);
void processSessionEvent(const SessionEvent &event, qint64 sessionId);
void processRoomEvent(const RoomEvent &event);
void processRoomCommand(const CommandContainer &cont, qint64 sessionId);
void processMessage(const IslMessage &item);
void sharedCtor(const QSslCertificate &cert, const QSslKey &privateKey);
public slots:
void initServer();
void initClient();
public:
IslInterface(int socketDescriptor, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server);
IslInterface(int _serverId,
const QString &peerHostName,
const QString &peerAddress,
int peerPort,
const QSslCertificate &peerCert,
const QSslCertificate &cert,
const QSslKey &privateKey,
Servatrice *_server);
~IslInterface();
void transmitMessage(const IslMessage &item);
};
#endif
+218
View File
@@ -0,0 +1,218 @@
/***************************************************************************
* Copyright (C) 2008 by Max-Wilhelm Bruker *
* brukie@laptop *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "servatrice.h"
#include "server_logger.h"
#include "settingscache.h"
#include "signalhandler.h"
#include "smtpclient.h"
#include "version_string.h"
#include <QCommandLineParser>
#include <QCoreApplication>
#include <QDateTime>
#include <QFile>
#include <QMetaType>
#include <QtGlobal>
#include <iostream>
#include <libcockatrice/rng/rng_sfmt.h>
#include <libcockatrice/utility/passwordhasher.h>
RNG_Abstract *rng;
ServerLogger *logger;
QThread *loggerThread;
SettingsCache *settingsCache;
SignalHandler *signalhandler;
SmtpClient *smtpClient;
/* Prototypes */
void testRNG();
void testHash();
void myMessageOutput(QtMsgType type, const QMessageLogContext &, const QString &msg);
void myMessageOutput2(QtMsgType type, const QMessageLogContext &, const QString &msg);
/* Implementations */
void testRNG()
{
const int n = 500000;
std::cerr << "Testing random number generator (n = " << n << " * bins)..." << std::endl;
const int min = 1;
const int minMax = 2;
const int maxMax = 10;
QVector<QVector<int>> numbers(maxMax - minMax + 1);
QVector<double> chisq(maxMax - minMax + 1);
for (int max = minMax; max <= maxMax; ++max) {
numbers[max - minMax] = rng->makeNumbersVector(n * (max - min + 1), min, max);
chisq[max - minMax] = rng->testRandom(numbers[max - minMax]);
}
for (int i = 0; i <= maxMax - min; ++i) {
std::cerr << (min + i);
for (auto &number : numbers) {
if (i < number.size()) {
std::cerr << "\t" << number[i];
} else {
std::cerr << "\t";
}
}
std::cerr << std::endl;
}
std::cerr << std::endl << "Chi^2 =";
for (double j : chisq) {
std::cerr << "\t" << QString::number(j, 'f', 3).toStdString();
}
std::cerr << std::endl << "k =";
for (int j = 0; j < chisq.size(); ++j) {
std::cerr << "\t" << (j - min + minMax);
}
std::cerr << std::endl << std::endl;
}
void testHash()
{
const int n = 5000;
std::cerr << "Benchmarking password hash function (n =" << n << ")..." << std::endl;
QDateTime startTime = QDateTime::currentDateTime();
for (int i = 0; i < n; ++i) {
PasswordHasher::computeHash("aaaaaa", "aaaaaaaaaaaaaaaa");
}
QDateTime endTime = QDateTime::currentDateTime();
std::cerr << startTime.secsTo(endTime) << "secs" << std::endl;
}
void myMessageOutput(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg)
{
logger->logMessage(msg);
}
void myMessageOutput2(QtMsgType /*type*/, const QMessageLogContext &, const QString &msg)
{
logger->logMessage(msg);
std::cerr << msg.toStdString() << std::endl;
}
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QCoreApplication::setOrganizationName("Cockatrice");
QCoreApplication::setApplicationName("Servatrice");
QCoreApplication::setApplicationVersion(VERSION_STRING);
QCommandLineParser parser;
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption testRandomOpt("test-random", "Test PRNG (chi^2)");
parser.addOption(testRandomOpt);
QCommandLineOption testHashFunctionOpt("test-hash", "Test password hash function");
parser.addOption(testHashFunctionOpt);
QCommandLineOption logToConsoleOpt("log-to-console", "Write server logs to console");
parser.addOption(logToConsoleOpt);
QCommandLineOption configPathOpt("config", "Read server configuration from <file>", "file", "");
parser.addOption(configPathOpt);
parser.process(app);
bool testRandom = parser.isSet(testRandomOpt);
bool testHashFunction = parser.isSet(testHashFunctionOpt);
bool logToConsole = parser.isSet(logToConsoleOpt);
QString configPath = parser.value(configPathOpt);
qRegisterMetaType<QList<int>>("QList<int>");
if (configPath.isEmpty()) {
configPath = SettingsCache::guessConfigurationPath();
} else if (!QFile::exists(configPath)) {
qCritical() << "Could not find configuration file at" << configPath;
return 1;
}
qWarning() << "Using configuration file: " << configPath;
settingsCache = new SettingsCache(configPath);
loggerThread = new QThread;
loggerThread->setObjectName("logger");
logger = new ServerLogger(logToConsole);
logger->moveToThread(loggerThread);
loggerThread->start();
QMetaObject::invokeMethod(logger, "startLog", Qt::BlockingQueuedConnection,
Q_ARG(QString, settingsCache->value("server/logfile", QString("server.log")).toString()));
if (logToConsole) {
qInstallMessageHandler(myMessageOutput);
} else {
qInstallMessageHandler(myMessageOutput2);
}
signalhandler = new SignalHandler();
rng = new RNG_SFMT;
std::cerr << "Servatrice " << VERSION_STRING << " starting." << std::endl;
std::cerr << "-------------------------" << std::endl;
if (testRandom) {
testRNG();
}
if (testHashFunction) {
testHash();
}
if (testRandom || testHashFunction) {
return 0;
}
smtpClient = new SmtpClient();
auto *server = new Servatrice();
QObject::connect(server, SIGNAL(destroyed()), &app, SLOT(quit()), Qt::QueuedConnection);
int retval = 0;
if (server->initServer()) {
std::cerr << "-------------------------" << std::endl;
std::cerr << "Server initialized." << std::endl;
qInstallMessageHandler(myMessageOutput);
retval = QCoreApplication::exec();
std::cerr << "Server quit." << std::endl;
std::cerr << "-------------------------" << std::endl;
}
delete smtpClient;
delete rng;
delete signalhandler;
delete settingsCache;
logger->deleteLater();
loggerThread->wait();
delete loggerThread;
// Delete all global objects allocated by libprotobuf.
google::protobuf::ShutdownProtobufLibrary();
QCoreApplication::quit();
return retval;
}
+14
View File
@@ -0,0 +1,14 @@
#ifndef MAIN_H
#define MAIN_H
class ServerLogger;
class QThread;
class SettingsCache;
class SmtpClient;
extern ServerLogger *logger;
extern QThread *loggerThread;
extern SettingsCache *settingsCache;
extern SmtpClient *smtpClient;
#endif
File diff suppressed because it is too large Load Diff
+286
View File
@@ -0,0 +1,286 @@
/***************************************************************************
* Copyright (C) 2008 by Max-Wilhelm Bruker *
* brukie@laptop *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef SERVATRICE_H
#define SERVATRICE_H
#include <QHostAddress>
#include <QMetaType>
#include <QMutex>
#include <QReadWriteLock>
#include <QSqlDatabase>
#include <QSslCertificate>
#include <QSslKey>
#include <QTcpServer>
#include <QWebSocketServer>
#include <server.h>
#include <utility>
Q_DECLARE_METATYPE(QSqlDatabase)
class QSqlQuery;
class QTimer;
class GameReplay;
class Servatrice;
class Servatrice_ConnectionPool;
class Servatrice_DatabaseInterface;
class AbstractServerSocketInterface;
class IslInterface;
class FeatureSet;
class Servatrice_GameServer : public QTcpServer
{
Q_OBJECT
private:
Servatrice *server;
QList<Servatrice_ConnectionPool *> connectionPools;
public:
Servatrice_GameServer(Servatrice *_server,
int _numberPools,
const QSqlDatabase &_sqlDatabase,
QObject *parent = nullptr);
~Servatrice_GameServer() override;
protected:
void incomingConnection(qintptr socketDescriptor) override;
Servatrice_ConnectionPool *findLeastUsedConnectionPool();
};
class Servatrice_WebsocketGameServer : public QWebSocketServer
{
Q_OBJECT
private:
Servatrice *server;
QList<Servatrice_ConnectionPool *> connectionPools;
public:
Servatrice_WebsocketGameServer(Servatrice *_server,
int _numberPools,
const QSqlDatabase &_sqlDatabase,
QObject *parent = nullptr);
~Servatrice_WebsocketGameServer() override;
protected:
Servatrice_ConnectionPool *findLeastUsedConnectionPool();
protected slots:
void onNewConnection();
};
class Servatrice_IslServer : public QTcpServer
{
Q_OBJECT
private:
Servatrice *server;
QSslCertificate cert;
QSslKey privateKey;
public:
Servatrice_IslServer(Servatrice *_server,
const QSslCertificate &_cert,
QSslKey _privateKey,
QObject *parent = nullptr)
: QTcpServer(parent), server(_server), cert(_cert), privateKey(std::move(_privateKey))
{
}
protected:
void incomingConnection(qintptr socketDescriptor) override;
};
class ServerProperties
{
public:
int id;
QSslCertificate cert;
QString hostname;
QHostAddress address;
int gamePort;
int controlPort;
ServerProperties(int _id,
const QSslCertificate &_cert,
QString _hostname,
const QHostAddress &_address,
int _gamePort,
int _controlPort)
: id(_id), cert(_cert), hostname(std::move(_hostname)), address(_address), gamePort(_gamePort),
controlPort(_controlPort)
{
}
};
class Servatrice : public Server
{
Q_OBJECT
public:
enum AuthenticationMethod
{
AuthenticationNone,
AuthenticationSql,
AuthenticationPassword
};
private slots:
void statusUpdate();
void shutdownTimeout();
protected:
void doSendIslMessage(const IslMessage &msg, int _serverId) override;
private:
enum DatabaseType
{
DatabaseNone,
DatabaseMySql
};
AuthenticationMethod authenticationMethod;
DatabaseType databaseType;
QTimer *pingClock, *statusUpdateClock;
Servatrice_GameServer *gameServer;
Servatrice_WebsocketGameServer *websocketGameServer;
Servatrice_IslServer *islServer;
mutable QMutex loginMessageMutex;
QString loginMessage;
QString dbPrefix;
QMap<QString, bool> serverRequiredFeatureList;
QString officialWarnings;
Servatrice_DatabaseInterface *servatriceDatabaseInterface;
int serverId;
int uptime;
QMutex txBytesMutex, rxBytesMutex;
quint64 txBytes, rxBytes;
QString shutdownReason;
int shutdownMinutes;
int nextShutdownMessageMinutes;
QTimer *shutdownTimer;
mutable QMutex serverListMutex;
QList<ServerProperties> serverList;
void updateServerList();
QMap<int, IslInterface *> islInterfaces;
QString getDBPrefixString() const;
QString getDBHostNameString() const;
QString getDBDatabaseNameString() const;
QString getDBUserNameString() const;
QString getDBPasswordString() const;
QString getRoomsMethodString() const;
QString getISLNetworkSSLCertFile() const;
QString getISLNetworkSSLKeyFile() const;
int getServerStatusUpdateTime() const;
int getNumberOfTCPPools() const;
int getServerTCPPort() const;
int getNumberOfWebSocketPools() const;
int getServerWebSocketPort() const;
int getISLNetworkPort() const;
bool getISLNetworkEnabled() const;
bool getEnableInternalSMTPClient() const;
QHostAddress getServerTCPHost() const;
QHostAddress getServerWebSocketHost() const;
public slots:
void scheduleShutdown(const QString &reason, int minutes);
void updateLoginMessage();
void setRequiredFeatures(const QString &featureList);
public:
explicit Servatrice(QObject *parent = nullptr);
~Servatrice() override;
bool initServer();
QMap<QString, bool> getServerRequiredFeatureList() const override
{
return serverRequiredFeatureList;
}
QString getServerName() const;
QString getLoginMessage() const override
{
QMutexLocker locker(&loginMessageMutex);
return loginMessage;
}
QString getRequiredFeatures() const override;
QString getAuthenticationMethodString() const;
QString getDBTypeString() const;
QString getDbPrefix() const
{
return dbPrefix;
}
QString getEmailBlackList() const;
QString getEmailWhiteList() const;
AuthenticationMethod getAuthenticationMethod() const
{
return authenticationMethod;
}
bool permitUnregisteredUsers() const override
{
return authenticationMethod != AuthenticationNone;
}
bool getGameShouldPing() const override
{
return true;
}
bool getClientIDRequiredEnabled() const override;
bool getRegOnlyServerEnabled() const override;
bool getMaxUserLimitEnabled() const override;
bool getStoreReplaysEnabled() const override;
bool getRegistrationEnabled() const;
bool getRequireEmailForRegistrationEnabled() const;
bool getRequireEmailActivationEnabled() const;
bool getEnableLogQuery() const override;
bool getEnableForgotPassword() const;
bool getEnableForgotPasswordChallenge() const;
bool getEnableAudit() const;
bool getEnableRegistrationAudit() const;
bool getEnableForgotPasswordAudit() const;
int getMinPasswordLength() const;
int getIdleClientTimeout() const override;
int getServerID() const override;
int getMaxGameInactivityTime() const override;
int getMaxPlayerInactivityTime() const override;
int getClientKeepAlive() const override;
int getMaxUsersPerAddress() const;
int getMessageCountingInterval() const override;
int getMaxMessageCountPerInterval() const override;
int getMaxMessageSizePerInterval() const override;
int getMaxGamesPerUser() const override;
int getCommandCountingInterval() const override;
int getMaxCommandCountPerInterval() const override;
int getMaxUserTotal() const override;
bool permitCreateGameAsJudge() const override;
int getMaxTcpUserLimit() const;
int getMaxWebSocketUserLimit() const;
int getUsersWithAddress(const QHostAddress &address) const;
int getMaxAccountsPerEmail() const;
int getForgotPasswordTokenLife() const;
QList<AbstractServerSocketInterface *> getUsersWithAddressAsList(const QHostAddress &address) const;
void incTxBytes(quint64 num);
void incRxBytes(quint64 num);
void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface);
bool islConnectionExists(int _serverId) const;
void addIslInterface(int _serverId, IslInterface *interface);
void removeIslInterface(int _serverId);
QReadWriteLock islLock;
QList<ServerProperties> getServerList() const;
};
#endif
@@ -0,0 +1,16 @@
#include "servatrice_connection_pool.h"
#include "servatrice_database_interface.h"
#include <QThread>
Servatrice_ConnectionPool::Servatrice_ConnectionPool(Servatrice_DatabaseInterface *_databaseInterface)
: databaseInterface(_databaseInterface), threaded(false), clientCount(0)
{
}
Servatrice_ConnectionPool::~Servatrice_ConnectionPool()
{
delete databaseInterface;
thread()->quit();
}
@@ -0,0 +1,46 @@
#ifndef SERVATRICE_CONNECTION_POOL_H
#define SERVATRICE_CONNECTION_POOL_H
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
class Servatrice_DatabaseInterface;
class Servatrice_ConnectionPool : public QObject
{
Q_OBJECT
private:
Servatrice_DatabaseInterface *databaseInterface;
bool threaded;
mutable QMutex clientCountMutex;
int clientCount;
public:
explicit Servatrice_ConnectionPool(Servatrice_DatabaseInterface *_databaseInterface);
~Servatrice_ConnectionPool() override;
Servatrice_DatabaseInterface *getDatabaseInterface() const
{
return databaseInterface;
}
int getClientCount() const
{
QMutexLocker locker(&clientCountMutex);
return clientCount;
}
void addClient()
{
QMutexLocker locker(&clientCountMutex);
++clientCount;
}
public slots:
void removeClient()
{
QMutexLocker locker(&clientCountMutex);
--clientCount;
}
};
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,152 @@
#ifndef SERVATRICE_DATABASE_INTERFACE_H
#define SERVATRICE_DATABASE_INTERFACE_H
#include <QChar>
#include <QHash>
#include <QObject>
#include <QSqlDatabase>
#include <libcockatrice/protocol/pb/serverinfo_chat_message.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_warning.pb.h>
#include <server.h>
#include <server_database_interface.h>
#define DATABASE_SCHEMA_VERSION 35
class Servatrice;
class Servatrice_DatabaseInterface : public Server_DatabaseInterface
{
Q_OBJECT
private:
int instanceId;
QSqlDatabase sqlDatabase;
QHash<QString, QSqlQuery *> preparedStatements;
Servatrice *server;
ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false);
/** Must be called after checkSql and server is known to be in auth mode. */
bool checkUserIsIdBanned(const QString &clientId, QString &banReason, int &banSecondsRemaining);
/** Must be called after checkSql and server is known to be in auth mode. */
bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining);
/** Must be called after checkSql and server is known to be in auth mode. */
bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining);
protected:
AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler,
const QString &user,
const QString &password,
const QString &clientId,
QString &reasonStr,
int &banSecondsLeft,
bool passwordNeedsHash) override;
public slots:
void initDatabase(const QSqlDatabase &_sqlDatabase);
public:
explicit Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server);
~Servatrice_DatabaseInterface() override;
bool initDatabase(const QString &type,
const QString &hostName,
const QString &databaseName,
const QString &userName,
const QString &password);
bool openDatabase();
bool checkSql();
QSqlQuery *prepareQuery(const QString &queryText);
bool execSqlQuery(QSqlQuery *query);
const QSqlDatabase &getDatabase()
{
return sqlDatabase;
}
bool activeUserExists(const QString &user) override;
bool userExists(const QString &user) override;
QString getUserSalt(const QString &user) override;
int getUserIdInDB(const QString &name);
QMap<QString, ServerInfo_User> getBuddyList(const QString &name) override;
QMap<QString, ServerInfo_User> getIgnoreList(const QString &name) override;
bool isInBuddyList(const QString &whoseList, const QString &who) override;
bool isInIgnoreList(const QString &whoseList, const QString &who) override;
ServerInfo_User getUserData(const QString &name, bool withId = false) override;
void storeGameInformation(const QString &roomName,
const QStringList &roomGameTypes,
const ServerInfo_Game &gameInfo,
const QSet<QString> &allPlayersEver,
const QSet<QString> &allSpectatorsEver,
const QList<GameReplay *> &replayList) override;
DeckList *getDeckFromDatabase(int deckId, int userId) override;
int getNextGameId() override;
int getNextReplayId() override;
int getActiveUserCount(QString connectionType = QString()) override;
qint64 startSession(const QString &userName,
const QString &address,
const QString &clientId,
const QString &connectionType) override;
void endSession(qint64 sessionId) override;
void clearSessionTables() override;
void lockSessionTables() override;
void unlockSessionTables() override;
bool userSessionExists(const QString &userName) override;
bool usernameIsValid(const QString &user, QString &error) override;
bool checkUserIsBanned(const QString &ipAddress,
const QString &userName,
const QString &clientId,
QString &banReason,
int &banSecondsRemaining) override;
int checkNumberOfUserAccounts(const QString &email) override;
bool registerUser(const QString &userName,
const QString &realName,
const QString &password,
bool passwordNeedsHash,
const QString &emailAddress,
const QString &country,
bool active = false) override;
bool activateUser(const QString &userName, const QString &token) override;
void updateUsersClientID(const QString &userName, const QString &userClientID) override;
void updateUsersLastLoginData(const QString &userName, const QString &clientVersion) override;
void logMessage(const int senderId,
const QString &senderName,
const QString &senderIp,
const QString &logMessage,
LogMessage_TargetType targetType,
const int targetId,
const QString &targetName) override;
bool changeUserPassword(const QString &user, const QString &password, bool passwordNeedsHash) override;
bool changeUserPassword(const QString &user,
const QString &oldPassword,
bool oldPasswordNeedsHash,
const QString &newPassword,
bool newPasswordNeedsHash) override;
QList<ServerInfo_Ban> getUserBanHistory(const QString userName);
bool
addWarning(const QString userName, const QString adminName, const QString warningReason, const QString clientID);
QList<ServerInfo_Warning> getUserWarnHistory(const QString userName);
QList<ServerInfo_ChatMessage> getMessageLogHistory(const QString &user,
const QString &ipaddress,
const QString &gamename,
const QString &gameid,
const QString &message,
bool &chat,
bool &game,
bool &room,
int &range,
int &maxresults);
bool addForgotPassword(const QString &user);
bool removeForgotPassword(const QString &user) override;
bool doesForgotPasswordExist(const QString &user);
bool updateUserToken(const QString &token, const QString &user);
bool validateTableColumnStringData(const QString &table,
const QString &column,
const QString &_user,
const QString &_datatocheck);
void addAuditRecord(const QString &user,
const QString &ipaddress,
const QString &clientid,
const QString &action,
const QString &details,
const bool &results);
};
#endif
+132
View File
@@ -0,0 +1,132 @@
#include "server_logger.h"
#include "settingscache.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <iostream>
ServerLogger::ServerLogger(bool _logToConsole, QObject *parent)
: QObject(parent), logToConsole(_logToConsole), flushRunning(false)
{
}
ServerLogger::~ServerLogger()
{
flushBuffer();
// This does not work with the destroyed() signal as this destructor is called after the main event loop is done.
thread()->quit();
}
void ServerLogger::startLog(const QString &logFileName)
{
if (!logFileName.isEmpty()) {
QFileInfo fi(logFileName);
QDir fileDir(fi.path());
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
std::cerr << "ERROR: logfile folder doesn't exist and i can't create it." << std::endl;
logFile = 0;
return;
}
logFile = new QFile(logFileName, this);
if (!logFile->open(QIODevice::Append)) {
std::cerr << "ERROR: can't open() logfile." << std::endl;
delete logFile;
logFile = 0;
return;
}
} else {
logFile = 0;
}
connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection);
}
void ServerLogger::logMessage(const QString &message, void *caller)
{
if (!logFile) {
return;
}
QString callerString;
if (caller) {
callerString = QString::number((qulonglong)caller, 16) + " ";
}
// filter out all log entries based on values in configuration file
bool shouldWeWriteLog = settingsCache->value("server/writelog", 1).toBool();
QString logFilters = settingsCache->value("server/logfilters").toString();
QStringList listlogFilters = logFilters.split(",", Qt::SkipEmptyParts);
bool shouldWeSkipLine = false;
if (!shouldWeWriteLog) {
return;
}
if (!logFilters.trimmed().isEmpty()) {
shouldWeSkipLine = true;
for (const QString &logFilter : listlogFilters) {
if (message.contains(logFilter, Qt::CaseInsensitive)) {
shouldWeSkipLine = false;
break;
}
}
}
if (shouldWeSkipLine) {
return;
}
bufferMutex.lock();
buffer.append(QDateTime::currentDateTime().toString() + " " + callerString + message);
bufferMutex.unlock();
emit sigFlushBuffer();
}
void ServerLogger::flushBuffer()
{
if (flushRunning) {
return;
}
flushRunning = true;
QTextStream stream(logFile);
forever
{
bufferMutex.lock();
if (buffer.isEmpty()) {
bufferMutex.unlock();
flushRunning = false;
return;
}
QString message = buffer.takeFirst();
bufferMutex.unlock();
stream << message << "\n";
stream.flush();
if (logToConsole) {
std::cout << message.toStdString() << std::endl;
}
}
}
void ServerLogger::rotateLogs()
{
if (!logFile) {
return;
}
flushBuffer();
logFile->close();
if (!logFile->open(QIODevice::Append)) {
std::cerr << "ERROR: Failed to open log file for writing!" << std::endl;
}
}
QFile *ServerLogger::logFile;
+36
View File
@@ -0,0 +1,36 @@
#ifndef SERVER_LOGGER_H
#define SERVER_LOGGER_H
#include <QMutex>
#include <QObject>
#include <QStringList>
#include <QThread>
#include <QWaitCondition>
class QFile;
class Server_ProtocolHandler;
class ServerLogger : public QObject
{
Q_OBJECT
public:
ServerLogger(bool _logToConsole, QObject *parent = 0);
~ServerLogger();
public slots:
void startLog(const QString &logFileName);
void logMessage(const QString &message, void *caller = 0);
void rotateLogs();
private slots:
void flushBuffer();
signals:
void sigFlushBuffer();
private:
bool logToConsole;
static QFile *logFile;
bool flushRunning;
QStringList buffer;
QMutex bufferMutex;
};
#endif
File diff suppressed because it is too large Load Diff
+255
View File
@@ -0,0 +1,255 @@
/***************************************************************************
* Copyright (C) 2008 by Max-Wilhelm Bruker *
* brukie@laptop *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#ifndef SERVERSOCKETINTERFACE_H
#define SERVERSOCKETINTERFACE_H
#include <QHostAddress>
#include <QMutex>
#include <QTcpSocket>
#include <QWebSocket>
#include <server_protocolhandler.h>
class Servatrice;
class Servatrice_DatabaseInterface;
class DeckList;
class ServerInfo_DeckStorage_Folder;
class Command_AddToList;
class Command_RemoveFromList;
class Command_DeckList;
class Command_DeckNewDir;
class Command_DeckDelDir;
class Command_DeckDel;
class Command_DeckDownload;
class Command_DeckUpload;
class Command_ReplayList;
class Command_ReplayDownload;
class Command_ReplayModifyMatch;
class Command_ReplayDeleteMatch;
class Command_ReplayGetCode;
class Command_ReplaySubmitCode;
class Command_BanFromServer;
class Command_UpdateServerMessage;
class Command_ShutdownServer;
class Command_ReloadConfig;
class Command_AccountEdit;
class Command_AccountImage;
class Command_AccountPassword;
class AbstractServerSocketInterface : public Server_ProtocolHandler
{
Q_OBJECT
protected slots:
void catchSocketError(QAbstractSocket::SocketError socketError);
void catchSocketDisconnected();
virtual void flushOutputQueue() = 0;
signals:
void outputQueueChanged();
void incTxBytes(qint64 amount);
protected:
void logDebugMessage(const QString &message);
bool tooManyRegistrationAttempts(const QString &ipAddress);
virtual void writeToSocket(QByteArray &data) = 0;
virtual void flushSocket() = 0;
Servatrice *servatrice;
QList<ServerMessage> outputQueue;
QMutex outputQueueMutex;
private:
Servatrice_DatabaseInterface *sqlInterface;
Response::ResponseCode cmdAddToList(const Command_AddToList &cmd, ResponseContainer &rc);
Response::ResponseCode cmdRemoveFromList(const Command_RemoveFromList &cmd, ResponseContainer &rc);
int getDeckPathId(int basePathId, QStringList path);
int getDeckPathId(const QString &path);
bool deckListHelper(int folderId, ServerInfo_DeckStorage_Folder *folder);
Response::ResponseCode cmdDeckList(const Command_DeckList &cmd, ResponseContainer &rc);
Response::ResponseCode cmdDeckNewDir(const Command_DeckNewDir &cmd, ResponseContainer &rc);
void deckDelDirHelper(int basePathId);
void sendServerMessage(const QString userName, const QString message);
Response::ResponseCode cmdDeckDelDir(const Command_DeckDelDir &cmd, ResponseContainer &rc);
Response::ResponseCode cmdDeckDel(const Command_DeckDel &cmd, ResponseContainer &rc);
Response::ResponseCode cmdDeckUpload(const Command_DeckUpload &cmd, ResponseContainer &rc);
DeckList *getDeckFromDatabase(int deckId);
Response::ResponseCode cmdDeckDownload(const Command_DeckDownload &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplayList(const Command_ReplayList &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplayDownload(const Command_ReplayDownload &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplayModifyMatch(const Command_ReplayModifyMatch &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplayDeleteMatch(const Command_ReplayDeleteMatch &cmd, ResponseContainer &rc);
QString createHashForReplay(int gameId);
Response::ResponseCode cmdReplayGetCode(const Command_ReplayGetCode &cmd, ResponseContainer &rc);
Response::ResponseCode cmdReplaySubmitCode(const Command_ReplaySubmitCode &cmd, ResponseContainer &rc);
Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &cmd, ResponseContainer &rc);
Response::ResponseCode cmdWarnUser(const Command_WarnUser &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetLogHistory(const Command_ViewLogHistory &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetBanHistory(const Command_GetBanHistory &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetWarnList(const Command_GetWarnList &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetWarnHistory(const Command_GetWarnHistory &cmd, ResponseContainer &rc);
Response::ResponseCode cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer &rc);
Response::ResponseCode cmdUpdateServerMessage(const Command_UpdateServerMessage &cmd, ResponseContainer &rc);
Response::ResponseCode cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc);
Response::ResponseCode cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /* rc */);
Response::ResponseCode cmdReloadConfig(const Command_ReloadConfig & /* cmd */, ResponseContainer & /*rc*/);
Response::ResponseCode cmdAdjustMod(const Command_AdjustMod &cmd, ResponseContainer & /*rc*/);
Response::ResponseCode cmdForgotPasswordRequest(const Command_ForgotPasswordRequest &cmd, ResponseContainer &rc);
Response::ResponseCode continuePasswordRequest(const QString &userName,
const QString &clientId,
ResponseContainer &rc,
bool challenged = false);
Response::ResponseCode cmdForgotPasswordReset(const Command_ForgotPasswordReset &cmd, ResponseContainer &rc);
Response::ResponseCode cmdForgotPasswordChallenge(const Command_ForgotPasswordChallenge &cmd,
ResponseContainer &rc);
Response::ResponseCode cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd, ResponseContainer &rc);
Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc);
Response::ResponseCode
processExtendedModeratorCommand(int cmdType, const ModeratorCommand &cmd, ResponseContainer &rc);
Response::ResponseCode processExtendedAdminCommand(int cmdType, const AdminCommand &cmd, ResponseContainer &rc);
Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc);
Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc);
bool isCardNameAllowed(const QString &cardName, const QString &cardProviderId);
Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &);
Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &);
Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &);
Response::ResponseCode cmdListCardArtRules(const Command_ListCardArtRules &, ResponseContainer &rc);
Response::ResponseCode cmdAccountPassword(const Command_AccountPassword &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGrantReplayAccess(const Command_GrantReplayAccess &cmd, ResponseContainer &rc);
Response::ResponseCode cmdForceActivateUser(const Command_ForceActivateUser &cmd, ResponseContainer &rc);
Response::ResponseCode cmdGetAdminNotes(const Command_GetAdminNotes &cmd, ResponseContainer &rc);
Response::ResponseCode cmdUpdateAdminNotes(const Command_UpdateAdminNotes &cmd, ResponseContainer &rc);
bool addAdminFlagToUser(const QString &user, int flag);
bool removeAdminFlagFromUser(const QString &user, int flag);
bool isPasswordLongEnough(const int passwordLength);
void removeSaidMessages(const QString &userName, int amount);
public:
AbstractServerSocketInterface(Servatrice *_server,
Servatrice_DatabaseInterface *_databaseInterface,
QObject *parent = 0);
~AbstractServerSocketInterface()
{
}
bool initSession();
virtual QHostAddress getPeerAddress() const = 0;
virtual QString getAddress() const = 0;
void transmitProtocolItem(const ServerMessage &item);
};
class TcpServerSocketInterface : public AbstractServerSocketInterface
{
Q_OBJECT
public:
TcpServerSocketInterface(Servatrice *_server,
Servatrice_DatabaseInterface *_databaseInterface,
QObject *parent = 0);
~TcpServerSocketInterface();
QHostAddress getPeerAddress() const
{
return socket->peerAddress();
}
QString getAddress() const
{
return socket->peerAddress().toString();
}
QString getConnectionType() const
{
return "tcp";
}
private:
QTcpSocket *socket;
QByteArray inputBuffer;
bool messageInProgress;
bool handshakeStarted;
int messageLength;
protected:
void writeToSocket(QByteArray &data)
{
socket->write(data);
}
void flushSocket()
{
socket->flush();
}
void initSessionDeprecated();
bool initTcpSession();
protected slots:
void readClient();
void flushOutputQueue();
public slots:
void initConnection(int socketDescriptor);
};
class WebsocketServerSocketInterface : public AbstractServerSocketInterface
{
Q_OBJECT
public:
WebsocketServerSocketInterface(Servatrice *_server,
Servatrice_DatabaseInterface *_databaseInterface,
QObject *parent = nullptr);
~WebsocketServerSocketInterface();
QHostAddress getPeerAddress() const
{
return address;
}
QString getAddress() const
{
return address.toString();
}
QString getConnectionType() const
{
return "websocket";
}
private:
QWebSocket *socket;
QHostAddress address;
protected:
void writeToSocket(QByteArray &data)
{
socket->sendBinaryMessage(data);
}
void flushSocket()
{
socket->flush();
}
bool initWebsocketSession();
protected slots:
void binaryMessageReceived(const QByteArray &message);
void flushOutputQueue();
public slots:
void initConnection(void *_socket);
};
#endif
+47
View File
@@ -0,0 +1,47 @@
#include "settingscache.h"
#include <QCoreApplication>
#include <QDebug>
#include <QFile>
#include <QStandardPaths>
SettingsCache::SettingsCache(const QString &fileName, QSettings::Format format, QObject *parent)
: QSettings(fileName, format, parent)
{
// first, figure out if we are running in portable mode
isPortableBuild = QFile::exists(qApp->applicationDirPath() + "/portable.dat");
QStringList disallowedRegExpStr = value("users/disallowedregexp", "").toString().split(",", Qt::SkipEmptyParts);
disallowedRegExpStr.removeDuplicates();
for (const QString &regExpStr : disallowedRegExpStr) {
disallowedRegExp.append(QRegularExpression(QString("\\A%1\\z").arg(regExpStr)));
}
}
QString SettingsCache::guessConfigurationPath()
{
const QString fileName = "servatrice.ini";
if (QFile::exists(qApp->applicationDirPath() + "/portable.dat")) {
qDebug() << "Portable mode enabled";
return fileName;
}
QString guessFileName;
// application directory path
guessFileName = QCoreApplication::applicationDirPath() + "/" + fileName;
if (QFile::exists(guessFileName)) {
return guessFileName;
}
#ifdef Q_OS_UNIX
// /etc
guessFileName = "/etc/servatrice/" + fileName;
if (QFile::exists(guessFileName)) {
return guessFileName;
}
#endif
guessFileName = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) + "/" + fileName;
return guessFileName;
}
+29
View File
@@ -0,0 +1,29 @@
#ifndef SERVATRICE_SETTINGSCACHE_H
#define SERVATRICE_SETTINGSCACHE_H
#include <QList>
#include <QRegularExpression>
#include <QSettings>
#include <QString>
class SettingsCache : public QSettings
{
Q_OBJECT
private:
bool isPortableBuild;
public:
SettingsCache(const QString &fileName = "servatrice.ini",
QSettings::Format format = QSettings::IniFormat,
QObject *parent = 0);
static QString guessConfigurationPath();
QList<QRegularExpression> disallowedRegExp;
bool getIsPortableBuild() const
{
return isPortableBuild;
}
};
extern SettingsCache *settingsCache;
#endif
+105
View File
@@ -0,0 +1,105 @@
#include "signalhandler.h"
#include "main.h"
#include "server_logger.h"
#include "settingscache.h"
#include <QSocketNotifier>
#ifdef Q_OS_UNIX
#include <cstdio>
#include <execinfo.h>
#include <iostream>
#include <signal.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif
#define SIGSEGV_TRACE_LINES 40
int SignalHandler::sigHupFD[2];
SignalHandler::SignalHandler(QObject *parent) : QObject(parent), snHup(nullptr)
{
#ifdef Q_OS_UNIX
::socketpair(AF_UNIX, SOCK_STREAM, 0, sigHupFD);
snHup = new QSocketNotifier(sigHupFD[1], QSocketNotifier::Read, this);
connect(snHup, SIGNAL(activated(int)), this, SLOT(internalSigHupHandler()));
struct sigaction hup;
hup.sa_handler = SignalHandler::sigHupHandler;
sigemptyset(&hup.sa_mask);
hup.sa_flags = 0;
hup.sa_flags |= SA_RESTART;
sigaction(SIGHUP, &hup, 0);
struct sigaction segv;
segv.sa_handler = SignalHandler::sigSegvHandler;
segv.sa_flags = SA_RESETHAND;
sigemptyset(&segv.sa_mask);
sigaction(SIGSEGV, &segv, 0);
sigaction(SIGABRT, &segv, 0);
signal(SIGPIPE, SIG_IGN);
#endif
}
void SignalHandler::sigHupHandler(int /* sig */)
{
#ifdef Q_OS_UNIX
char a = 1;
ssize_t writeValue = ::write(sigHupFD[0], &a, sizeof(a));
Q_UNUSED(writeValue);
#endif
}
void SignalHandler::internalSigHupHandler()
{
snHup->setEnabled(false);
#ifdef Q_OS_UNIX
char tmp;
ssize_t readValue = ::read(sigHupFD[1], &tmp, sizeof(tmp));
Q_UNUSED(readValue);
std::cerr << "Received SIGHUP" << std::endl;
#endif
logger->logMessage("Received SIGHUP, rotating logs and reloading configuration", this);
logger->rotateLogs();
settingsCache->sync();
snHup->setEnabled(true);
}
#ifdef Q_OS_UNIX
void SignalHandler::sigSegvHandler(int sig)
{
void *array[SIGSEGV_TRACE_LINES];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, SIGSEGV_TRACE_LINES);
// print out all the frames to stderr
fprintf(stderr, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, STDERR_FILENO);
if (sig == SIGSEGV) {
logger->logMessage("CRASH: SIGSEGV");
} else if (sig == SIGABRT) {
logger->logMessage("CRASH: SIGABRT");
}
logger->deleteLater();
loggerThread->wait();
delete loggerThread;
raise(sig);
}
#else
void SignalHandler::sigSegvHandler(int /* sig */)
{
}
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef SIGNALHANDLER_H
#define SIGNALHANDLER_H
#include <QObject>
class QSocketNotifier;
class SignalHandler : public QObject
{
Q_OBJECT
public:
SignalHandler(QObject *parent = 0);
~SignalHandler()
{
}
static void sigHupHandler(int /* sig */);
static void sigSegvHandler(int sig);
private:
static int sigHupFD[2];
QSocketNotifier *snHup;
private slots:
void internalSigHupHandler();
};
#endif
+208
View File
@@ -0,0 +1,208 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtCore module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTGLOBAL_H
#define QXTGLOBAL_H
#include <QtGlobal>
#define QXT_VERSION 0x000602
#define QXT_VERSION_STR "0.6.2"
#define QXT_STATIC
//--------------------------global macros------------------------------
#ifndef QXT_NO_MACROS
#endif // QXT_NO_MACROS
//--------------------------export macros------------------------------
#define QXT_DLLEXPORT DO_NOT_USE_THIS_ANYMORE
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_CORE)
# define QXT_CORE_EXPORT Q_DECL_EXPORT
# else
# define QXT_CORE_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_CORE_EXPORT
#endif // BUILD_QXT_CORE
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_GUI)
# define QXT_GUI_EXPORT Q_DECL_EXPORT
# else
# define QXT_GUI_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_GUI_EXPORT
#endif // BUILD_QXT_GUI
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_NETWORK)
# define QXT_NETWORK_EXPORT Q_DECL_EXPORT
# else
# define QXT_NETWORK_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_NETWORK_EXPORT
#endif // BUILD_QXT_NETWORK
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_SQL)
# define QXT_SQL_EXPORT Q_DECL_EXPORT
# else
# define QXT_SQL_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_SQL_EXPORT
#endif // BUILD_QXT_SQL
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_WEB)
# define QXT_WEB_EXPORT Q_DECL_EXPORT
# else
# define QXT_WEB_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_WEB_EXPORT
#endif // BUILD_QXT_WEB
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_BERKELEY)
# define QXT_BERKELEY_EXPORT Q_DECL_EXPORT
# else
# define QXT_BERKELEY_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_BERKELEY_EXPORT
#endif // BUILD_QXT_BERKELEY
#if !defined(QXT_STATIC)
# if defined(BUILD_QXT_ZEROCONF)
# define QXT_ZEROCONF_EXPORT Q_DECL_EXPORT
# else
# define QXT_ZEROCONF_EXPORT Q_DECL_IMPORT
# endif
#else
# define QXT_ZEROCONF_EXPORT
#endif // QXT_ZEROCONF_EXPORT
#if defined BUILD_QXT_CORE || defined BUILD_QXT_GUI || defined BUILD_QXT_SQL || defined BUILD_QXT_NETWORK || defined BUILD_QXT_WEB || defined BUILD_QXT_BERKELEY || defined BUILD_QXT_ZEROCONF
# define BUILD_QXT
#endif
QXT_CORE_EXPORT const char* qxtVersion();
#ifndef QT_BEGIN_NAMESPACE
#define QT_BEGIN_NAMESPACE
#endif
#ifndef QT_END_NAMESPACE
#define QT_END_NAMESPACE
#endif
#ifndef QT_FORWARD_DECLARE_CLASS
#define QT_FORWARD_DECLARE_CLASS(Class) class Class;
#endif
/****************************************************************************
** This file is derived from code bearing the following notice:
** The sole author of this file, Adam Higerd, has explicitly disclaimed all
** copyright interest and protection for the content within. This file has
** been placed in the public domain according to United States copyright
** statute and case law. In jurisdictions where this public domain dedication
** is not legally recognized, anyone who receives a copy of this file is
** permitted to use, modify, duplicate, and redistribute this file, in whole
** or in part, with no restrictions or conditions. In these jurisdictions,
** this file shall be copyright (C) 2006-2008 by Adam Higerd.
****************************************************************************/
#define QXT_DECLARE_PRIVATE(PUB) friend class PUB##Private; QxtPrivateInterface<PUB, PUB##Private> qxt_d;
#define QXT_DECLARE_PUBLIC(PUB) friend class PUB;
#define QXT_INIT_PRIVATE(PUB) qxt_d.setPublic(this);
#define QXT_D(PUB) PUB##Private& d = qxt_d()
#define QXT_P(PUB) PUB& p = qxt_p()
template <typename PUB>
class QxtPrivate
{
public:
virtual ~QxtPrivate()
{}
inline void QXT_setPublic(PUB* pub)
{
qxt_p_ptr = pub;
}
protected:
inline PUB& qxt_p()
{
return *qxt_p_ptr;
}
inline const PUB& qxt_p() const
{
return *qxt_p_ptr;
}
private:
PUB* qxt_p_ptr;
};
template <typename PUB, typename PVT>
class QxtPrivateInterface
{
friend class QxtPrivate<PUB>;
public:
QxtPrivateInterface()
{
pvt = new PVT;
}
~QxtPrivateInterface()
{
delete pvt;
}
inline void setPublic(PUB* pub)
{
pvt->QXT_setPublic(pub);
}
inline PVT& operator()()
{
return *static_cast<PVT*>(pvt);
}
inline const PVT& operator()() const
{
return *static_cast<PVT*>(pvt);
}
private:
QxtPrivateInterface(const QxtPrivateInterface&) { }
QxtPrivateInterface& operator=(const QxtPrivateInterface&) { }
QxtPrivate<PUB>* pvt;
};
#endif // QXT_GLOBAL
+210
View File
@@ -0,0 +1,210 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtCore module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#include "qxthmac.h"
#include <QtGlobal>
/*
\class QxtHmac
\inmodule QxtCore
\brief The QxtHmac class calculates keyed-Hash Message Authentication Codes
HMAC is a well-known algorithm for generating a message authentication code (MAC) that can be used to verify the
integrity and authenticity of a message.
This class requires Qt 4.3.0 or greater.
To verify a message, the sender creates a MAC using a key, which is a secret known only to the sender and recipient,
and the content of the message. This MAC is then sent along with the message. The recipient then creates another MAC
using the shared key and the content of the message. If the two codes match, the message is verified.
HMAC has been used as a password encryption scheme. The final output of the HMAC algorithm depends on the shared key
and an inner hash. This inner hash is generated from the message content and the key. To use HMAC as a password
scheme, the key should be the username; the message should be the user's password. The authenticating party (for
instance, a login server) only needs to store this inner hash generated by the innerHash() function. When requesting
authentication, the user calculates a HMAC using this key and message and sends his username and this HMAC to the
authenticator. The authenticator can then use verify() using the provided HMAC and the stored inner hash. When using
this scheme, the password is never stored or transmitted in plain text.
*/
#ifndef QXT_DOXYGEN_RUN
class QxtHmacPrivate : public QxtPrivate<QxtHmac>
{
public:
QXT_DECLARE_PUBLIC(QxtHmac)
QxtHmacPrivate() : ohash(0), ihash(0) {}
~QxtHmacPrivate()
{
// deleting NULL is safe, so no tests are needed here
delete ohash;
delete ihash;
}
QCryptographicHash* ohash;
QCryptographicHash* ihash;
QByteArray opad, ipad, result;
QCryptographicHash::Algorithm algorithm;
};
#endif
/*!
* Constructs a QxtHmac object using the specified algorithm.
*/
QxtHmac::QxtHmac(QCryptographicHash::Algorithm algorithm)
{
QXT_INIT_PRIVATE(QxtHmac);
qxt_d().ohash = new QCryptographicHash(algorithm);
qxt_d().ihash = new QCryptographicHash(algorithm);
qxt_d().algorithm = algorithm;
}
/*!
* Sets the shared secret key for the message authentication code.
*
* Any data that had been processed using addData() will be discarded.
*/
void QxtHmac::setKey(QByteArray key)
{
// We make the assumption that all hashes use a 512-bit block size; as of Qt 4.4.0 this is true of all supported hash functions
QxtHmacPrivate* d = &qxt_d();
d->opad = QByteArray(64, 0x5c);
d->ipad = QByteArray(64, 0x36);
if (key.size() > 64)
{
key = QCryptographicHash::hash(key, d->algorithm);
}
for (int i = key.size() - 1; i >= 0; --i)
{
d->opad[i] = d->opad[i] ^ key[i];
d->ipad[i] = d->ipad[i] ^ key[i];
}
reset();
}
/*!
* Resets the object.
*
* Any data that had been processed using addData() will be discarded.
* The key, if set, will be preserved.
*/
void QxtHmac::reset()
{
QxtHmacPrivate* d = &qxt_d();
d->ihash->reset();
d->ihash->addData(d->ipad);
}
/*!
* Returns the inner hash of the HMAC function.
*
* This hash can be stored in lieu of the shared secret on the authenticating side
* and used for verifying an HMAC code. When used in this manner, HMAC can be used
* to provide a form of secure password authentication. See the documentation above
* for details.
*/
QByteArray QxtHmac::innerHash() const
{
return qxt_d().ihash->result();
}
/*!
* Returns the authentication code for the message.
*/
QByteArray QxtHmac::result()
{
QxtHmacPrivate* d = &qxt_d();
Q_ASSERT(d->opad.size());
if (d->result.size())
return d->result;
d->ohash->reset();
d->ohash->addData(d->opad);
d->ohash->addData(innerHash());
d->result = d->ohash->result();
return d->result;
}
/*!
* Verifies the authentication code against a known inner hash.
*
* \sa innerHash()
*/
bool QxtHmac::verify(const QByteArray& otherInner)
{
result(); // populates d->result
QxtHmacPrivate* d = &qxt_d();
d->ohash->reset();
d->ohash->addData(d->opad);
d->ohash->addData(otherInner);
return d->result == d->ohash->result();
}
/*!
* Adds the provided data to the message to be authenticated.
*/
void QxtHmac::addData(const char* data, int length)
{
Q_ASSERT(qxt_d().opad.size());
#if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0))
qxt_d().ihash->addData(QByteArrayView(data, length));
#else
qxt_d().ihash->addData(data, length);
#endif
qxt_d().result.clear();
}
/*!
* Adds the provided data to the message to be authenticated.
*/
void QxtHmac::addData(const QByteArray& data)
{
addData(data.constData(), data.size());
}
/*!
* Returns the HMAC of the provided data using the specified key and hashing algorithm.
*/
QByteArray QxtHmac::hash(const QByteArray& key, const QByteArray& data, Algorithm algorithm)
{
QxtHmac hmac(algorithm);
hmac.setKey(key);
hmac.addData(data);
return hmac.result();
}
/*!
* Verifies a HMAC against a known key and inner hash using the specified hashing algorithm.
*/
bool QxtHmac::verify(const QByteArray& key, const QByteArray& hmac, const QByteArray& inner, Algorithm algorithm)
{
QxtHmac calc(algorithm);
calc.setKey(key);
QxtHmacPrivate* d = &calc.qxt_d();
d->ohash->reset();
d->ohash->addData(d->opad);
d->ohash->addData(inner);
return hmac == d->ohash->result();
}
+58
View File
@@ -0,0 +1,58 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtCore module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTHMAC_H
#define QXTHMAC_H
#include <QtGlobal>
#include <QCryptographicHash>
#include "qxtglobal.h"
class QxtHmacPrivate;
class QXT_CORE_EXPORT QxtHmac
{
public:
typedef QCryptographicHash::Algorithm Algorithm;
QxtHmac(QCryptographicHash::Algorithm algorithm);
void setKey(QByteArray key);
void reset();
void addData(const char* data, int length);
void addData(const QByteArray& data);
QByteArray innerHash() const;
QByteArray result();
bool verify(const QByteArray& otherInner);
static QByteArray hash(const QByteArray& key, const QByteArray& data, Algorithm algorithm);
static bool verify(const QByteArray& key, const QByteArray& hmac, const QByteArray& inner, Algorithm algorithm);
private:
QXT_DECLARE_PRIVATE(QxtHmac)
};
#endif
+35
View File
@@ -0,0 +1,35 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTMAIL_P_H
#define QXTMAIL_P_H
#include <QByteArray>
#define QXT_MUST_QP(x) (x < char(32) || x > char(126) || x == '=' || x == '?')
QByteArray qxt_fold_mime_header(const QString &key,
const QString &value,
const QByteArray &prefix = QByteArray());
#endif // QXTMAIL_P_H
@@ -0,0 +1,209 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
/*!
* \class QxtMailAttachment
* \inmodule QxtNetwork
* \brief The QxtMailAttachment class represents an attachement to a QxtMailMessage
*/
#include "qxtmailattachment.h"
#include "qxtmail_p.h"
#include <QBuffer>
#include <QPointer>
#include <QFile>
#include <QtDebug>
struct QxtMailAttachmentPrivate : public QSharedData
{
QHash<QString, QString> extraHeaders;
QString contentType;
QPointer<QIODevice> content;
bool deleteContent;
QxtMailAttachmentPrivate()
{
content = 0;
deleteContent = false;
contentType = "text/plain";
}
~QxtMailAttachmentPrivate()
{
if (deleteContent && content)
content->deleteLater();
deleteContent = false;
content = 0;
}
};
QxtMailAttachment::QxtMailAttachment()
{
qxt_d = new QxtMailAttachmentPrivate;
}
QxtMailAttachment::QxtMailAttachment(const QxtMailAttachment& other) : qxt_d(other.qxt_d)
{
// trivial copy constructor
}
QxtMailAttachment::QxtMailAttachment(const QByteArray& content, const QString& contentType)
{
qxt_d = new QxtMailAttachmentPrivate;
setContentType(contentType);
setContent(content);
}
QxtMailAttachment::QxtMailAttachment(QIODevice* content, const QString& contentType)
{
qxt_d = new QxtMailAttachmentPrivate;
setContentType(contentType);
setContent(content);
}
QxtMailAttachment& QxtMailAttachment::operator=(const QxtMailAttachment & other)
{
qxt_d = other.qxt_d;
return *this;
}
QxtMailAttachment::~QxtMailAttachment()
{
// trivial destructor
}
QIODevice* QxtMailAttachment::content() const
{
return qxt_d->content;
}
void QxtMailAttachment::setContent(const QByteArray& content)
{
if (qxt_d->deleteContent && qxt_d->content)
qxt_d->content->deleteLater();
qxt_d->content = new QBuffer;
static_cast<QBuffer*>(qxt_d->content.data())->setData(content);
}
void QxtMailAttachment::setContent(QIODevice* content)
{
if (qxt_d->deleteContent && qxt_d->content)
qxt_d->content->deleteLater();
qxt_d->content = content;
}
bool QxtMailAttachment::deleteContent() const
{
return qxt_d->deleteContent;
}
void QxtMailAttachment::setDeleteContent(bool enable)
{
qxt_d->deleteContent = enable;
}
QString QxtMailAttachment::contentType() const
{
return qxt_d->contentType;
}
void QxtMailAttachment::setContentType(const QString& contentType)
{
qxt_d->contentType = contentType;
}
QHash<QString, QString> QxtMailAttachment::extraHeaders() const
{
return qxt_d->extraHeaders;
}
QByteArray QxtMailAttachment::extraHeader(const QString& key) const
{
return qxt_d->extraHeaders[key.toLower()].toLatin1();
}
bool QxtMailAttachment::hasExtraHeader(const QString& key) const
{
return qxt_d->extraHeaders.contains(key.toLower());
}
void QxtMailAttachment::setExtraHeader(const QString& key, const QString& value)
{
qxt_d->extraHeaders[key.toLower()] = value;
}
void QxtMailAttachment::setExtraHeaders(const QHash<QString, QString>& a)
{
QHash<QString, QString>& headers = qxt_d->extraHeaders;
headers.clear();
for (const QString& key: a.keys())
{
headers[key.toLower()] = a[key];
}
}
void QxtMailAttachment::removeExtraHeader(const QString& key)
{
qxt_d->extraHeaders.remove(key.toLower());
}
QByteArray QxtMailAttachment::mimeData()
{
QIODevice* c = content();
if (!c)
{
qWarning() << "QxtMailAttachment::mimeData(): Content not set or already output";
return QByteArray();
}
if (!c->isOpen() && !c->open(QIODevice::ReadOnly))
{
qWarning() << "QxtMailAttachment::mimeData(): Cannot open content for reading";
return QByteArray();
}
QByteArray rv = "Content-Type: " + qxt_d->contentType.toLatin1() + "\r\nContent-Transfer-Encoding: base64\r\n";
for(const QString& r: qxt_d->extraHeaders.keys())
{
rv += qxt_fold_mime_header(r.toLatin1(), extraHeader(r));
}
rv += "\r\n";
while (!c->atEnd())
{
rv += c->read(57).toBase64() + "\r\n";
}
setContent((QIODevice*)0);
return rv;
}
QxtMailAttachment QxtMailAttachment::fromFile(const QString& filename)
{
QxtMailAttachment rv(new QFile(filename));
rv.setDeleteContent(true);
return rv;
}
@@ -0,0 +1,73 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTMAILATTACHMENT_H
#define QXTMAILATTACHMENT_H
#include "qxtglobal.h"
#include <QByteArray>
#include <QHash>
#include <QIODevice>
#include <QMetaType>
#include <QSharedDataPointer>
#include <QStringList>
struct QxtMailAttachmentPrivate;
class QXT_NETWORK_EXPORT QxtMailAttachment
{
public:
QxtMailAttachment();
QxtMailAttachment(const QxtMailAttachment &other);
QxtMailAttachment(const QByteArray &content, const QString &contentType = QString("application/octet-stream"));
QxtMailAttachment(QIODevice *content, const QString &contentType = QString("application/octet-stream"));
QxtMailAttachment &operator=(const QxtMailAttachment &other);
~QxtMailAttachment();
static QxtMailAttachment fromFile(const QString &filename);
QIODevice *content() const;
void setContent(const QByteArray &content);
void setContent(QIODevice *content);
bool deleteContent() const;
void setDeleteContent(bool enable);
QString contentType() const;
void setContentType(const QString &contentType);
QHash<QString, QString> extraHeaders() const;
QByteArray extraHeader(const QString &) const;
bool hasExtraHeader(const QString &) const;
void setExtraHeader(const QString &key, const QString &value);
void setExtraHeaders(const QHash<QString, QString> &);
void removeExtraHeader(const QString &key);
QByteArray mimeData();
private:
QSharedDataPointer<QxtMailAttachmentPrivate> qxt_d;
};
Q_DECLARE_TYPEINFO(QxtMailAttachment, Q_MOVABLE_TYPE);
#endif // QXTMAILATTACHMENT_H
+475
View File
@@ -0,0 +1,475 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
/*!
* \class QxtMailMessage
* \inmodule QxtNetwork
* \brief The QxtMailMessage class encapsulates an e-mail according to RFC 2822 and related specifications
*/
//! \todo {implicitshared}
#include "qxtmailmessage.h"
#include "qxtmail_p.h"
#include <QDir>
#include <QUuid>
#include <QtDebug>
static bool isASCII(const QString &string) {
for(const QChar &chr : string){
if(chr.unicode() > 0x7f)
return false;
}
return true;
}
struct QxtMailMessagePrivate : public QSharedData
{
QxtMailMessagePrivate()
{
}
QxtMailMessagePrivate(const QxtMailMessagePrivate &other)
: QSharedData(other), rcptTo(other.rcptTo), rcptCc(other.rcptCc), rcptBcc(other.rcptBcc),
subject(other.subject), body(other.body), sender(other.sender), extraHeaders(other.extraHeaders),
attachments(other.attachments)
{
}
QStringList rcptTo, rcptCc, rcptBcc;
QString subject, body, sender;
QHash<QString, QString> extraHeaders;
QHash<QString, QxtMailAttachment> attachments;
mutable QByteArray boundary;
};
QxtMailMessage::QxtMailMessage()
{
qxt_d = new QxtMailMessagePrivate;
}
QxtMailMessage::QxtMailMessage(const QxtMailMessage &other) : qxt_d(other.qxt_d)
{
// trivial copy constructor
}
QxtMailMessage::QxtMailMessage(const QString &sender, const QString &recipient)
{
qxt_d = new QxtMailMessagePrivate;
setSender(sender);
addRecipient(recipient);
}
QxtMailMessage::~QxtMailMessage()
{
// trivial destructor
}
QxtMailMessage &QxtMailMessage::operator=(const QxtMailMessage &other)
{
qxt_d = other.qxt_d;
return *this;
}
QString QxtMailMessage::sender() const
{
return qxt_d->sender;
}
void QxtMailMessage::setSender(const QString &a)
{
qxt_d->sender = a;
}
QString QxtMailMessage::subject() const
{
return qxt_d->subject;
}
void QxtMailMessage::setSubject(const QString &a)
{
qxt_d->subject = a;
}
QString QxtMailMessage::body() const
{
return qxt_d->body;
}
void QxtMailMessage::setBody(const QString &a)
{
qxt_d->body = a;
}
QStringList QxtMailMessage::recipients(QxtMailMessage::RecipientType type) const
{
if (type == Bcc)
return qxt_d->rcptBcc;
if (type == Cc)
return qxt_d->rcptCc;
return qxt_d->rcptTo;
}
void QxtMailMessage::addRecipient(const QString &a, QxtMailMessage::RecipientType type)
{
if (type == Bcc)
qxt_d->rcptBcc.append(a);
else if (type == Cc)
qxt_d->rcptCc.append(a);
else
qxt_d->rcptTo.append(a);
}
void QxtMailMessage::removeRecipient(const QString &a)
{
qxt_d->rcptTo.removeAll(a);
qxt_d->rcptCc.removeAll(a);
qxt_d->rcptBcc.removeAll(a);
}
QHash<QString, QString> QxtMailMessage::extraHeaders() const
{
return qxt_d->extraHeaders;
}
QByteArray QxtMailMessage::extraHeader(const QString &key) const
{
return qxt_d->extraHeaders[key.toLower()].toLatin1();
}
bool QxtMailMessage::hasExtraHeader(const QString &key) const
{
return qxt_d->extraHeaders.contains(key.toLower());
}
void QxtMailMessage::setExtraHeader(const QString &key, const QString &value)
{
qxt_d->extraHeaders[key.toLower()] = value;
}
void QxtMailMessage::setExtraHeaders(const QHash<QString, QString> &a)
{
QHash<QString, QString> &headers = qxt_d->extraHeaders;
headers.clear();
for (const QString &key : a.keys()) {
headers[key.toLower()] = a[key];
}
}
void QxtMailMessage::removeExtraHeader(const QString &key)
{
qxt_d->extraHeaders.remove(key.toLower());
}
QHash<QString, QxtMailAttachment> QxtMailMessage::attachments() const
{
return qxt_d->attachments;
}
QxtMailAttachment QxtMailMessage::attachment(const QString &filename) const
{
return qxt_d->attachments[filename];
}
void QxtMailMessage::addAttachment(const QString &filename, const QxtMailAttachment &attach)
{
if (qxt_d->attachments.contains(filename)) {
qWarning() << "QxtMailMessage::addAttachment: " << filename << " already in use";
int i = 1;
while (qxt_d->attachments.contains(filename + "." + QString::number(i))) {
i++;
}
qxt_d->attachments[filename + "." + QString::number(i)] = attach;
} else {
qxt_d->attachments[filename] = attach;
}
}
void QxtMailMessage::removeAttachment(const QString &filename)
{
qxt_d->attachments.remove(filename);
}
QByteArray qxt_fold_mime_header(const QString &key, const QString &value, const QByteArray &prefix)
{
QByteArray rv = "";
QByteArray line = key.toLatin1() + ": ";
if (!prefix.isEmpty())
line += prefix;
if (!value.contains("=?") && isASCII(value)) {
bool firstWord = true;
for (const QByteArray &word : value.toLatin1().split(' ')) {
if (line.size() > 78) {
rv = rv + line + "\r\n";
line.clear();
}
if (firstWord)
line += word;
else
line += " " + word;
firstWord = false;
}
} else {
// The text cannot be losslessly encoded as Latin-1. Therefore, we
// must use quoted-printable or base64 encoding. This is a quick
// heuristic based on the first 100 characters to see which
// encoding to use.
QByteArray utf8 = value.toUtf8();
int ct = utf8.length();
int nonAscii = 0;
for (int i = 0; i < ct && i < 100; i++) {
if (QXT_MUST_QP(utf8[i]))
nonAscii++;
}
if (nonAscii > 20) {
// more than 20%-ish non-ASCII characters: use base64
QByteArray base64 = utf8.toBase64();
ct = base64.length();
line += "=?utf-8?b?";
for (int i = 0; i < ct; i += 4) {
if (line.length() > 72) {
rv += line + "?\r\n";
line = " =?utf-8?b?";
}
line = line + base64.mid(i, 4);
}
} else {
// otherwise use Q-encoding
line += "=?utf-8?q?";
for (int i = 0; i < ct; i++) {
if (line.length() > 73) {
rv += line + "?\r\n";
line = " =?utf-8?q?";
}
if (QXT_MUST_QP(utf8[i]) || utf8[i] == ' ') {
line += "=" + utf8.mid(i, 1).toHex().toUpper();
} else {
line += utf8[i];
}
}
}
line += "?="; // end encoded-word atom
}
return rv + line + "\r\n";
}
QByteArray QxtMailMessage::rfc2822() const
{
// Use quoted-printable if requested
bool useQuotedPrintable = (extraHeader("Content-Transfer-Encoding").toLower() == "quoted-printable");
// Use base64 if requested
bool useBase64 = (extraHeader("Content-Transfer-Encoding").toLower() == "base64");
// Check to see if plain text is ASCII-clean; assume it isn't if QP or base64 was requested
bool bodyIsAscii = !useQuotedPrintable && !useBase64 && isASCII(body());
QHash<QString, QxtMailAttachment> attach = attachments();
QByteArray rv;
if (!sender().isEmpty() && !hasExtraHeader("From")) {
rv += qxt_fold_mime_header("From", sender());
}
if (!qxt_d->rcptTo.isEmpty()) {
rv += qxt_fold_mime_header("To", qxt_d->rcptTo.join(", "));
}
if (!qxt_d->rcptCc.isEmpty()) {
rv += qxt_fold_mime_header("Cc", qxt_d->rcptCc.join(", "));
}
if (!subject().isEmpty()) {
rv += qxt_fold_mime_header("Subject", subject());
}
if (!bodyIsAscii) {
if (!hasExtraHeader("MIME-Version") && !attach.count())
rv += "MIME-Version: 1.0\r\n";
// If no transfer encoding has been requested, guess.
// Heuristic: If >20% of the first 100 characters aren't
// 7-bit clean, use base64, otherwise use Q-P.
if (!bodyIsAscii && !useQuotedPrintable && !useBase64) {
QString b = body();
int nonAscii = 0;
int ct = b.length();
for (int i = 0; i < ct && i < 100; i++) {
if (QXT_MUST_QP(b[i]))
nonAscii++;
}
useQuotedPrintable = !(nonAscii > 20);
useBase64 = !useQuotedPrintable;
}
}
if (attach.count()) {
if (qxt_d->boundary.isEmpty())
qxt_d->boundary = QUuid::createUuid().toString().toLatin1().replace("{", "").replace("}", "");
if (!hasExtraHeader("MIME-Version"))
rv += "MIME-Version: 1.0\r\n";
if (!hasExtraHeader("Content-Type"))
rv += "Content-Type: multipart/mixed; boundary=" + qxt_d->boundary + "\r\n";
} else if (!bodyIsAscii && !hasExtraHeader("Content-Transfer-Encoding")) {
if (!useQuotedPrintable) {
// base64
rv += "Content-Transfer-Encoding: base64\r\n";
} else {
// quoted-printable
rv += "Content-Transfer-Encoding: quoted-printable\r\n";
}
}
for (const QString &r : qxt_d->extraHeaders.keys()) {
if ((r.toLower() == "content-type" || r.toLower() == "content-transfer-encoding") && attach.count()) {
// Since we're in multipart mode, we'll be outputting this later
continue;
}
rv += qxt_fold_mime_header(r.toLatin1(), extraHeader(r));
}
rv += "\r\n";
if (attach.count()) {
// we're going to have attachments, so output the lead-in for the message body
rv += "This is a message with multiple parts in MIME format.\r\n";
rv += "--" + qxt_d->boundary + "\r\nContent-Type: ";
if (hasExtraHeader("Content-Type"))
rv += extraHeader("Content-Type") + "\r\n";
else
rv += "text/plain; charset=UTF-8\r\n";
if (hasExtraHeader("Content-Transfer-Encoding")) {
rv += "Content-Transfer-Encoding: " + extraHeader("Content-Transfer-Encoding") + "\r\n";
} else if (!bodyIsAscii) {
if (!useQuotedPrintable) {
// base64
rv += "Content-Transfer-Encoding: base64\r\n";
} else {
// quoted-printable
rv += "Content-Transfer-Encoding: quoted-printable\r\n";
}
}
rv += "\r\n";
}
if (bodyIsAscii) {
QByteArray b = body().toLatin1();
int len = b.length();
QByteArray line = "";
QByteArray word = "";
for (int i = 0; i < len; i++) {
if (b[i] == '\n' || b[i] == '\r') {
if (line.isEmpty()) {
line = word;
word = "";
} else if (line.length() + word.length() + 1 <= 78) {
line = line + ' ' + word;
word = "";
}
if (line.isEmpty())
continue;
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
if ((b[i + 1] == '\n' || b[i + 1] == '\r') && b[i] != b[i + 1]) {
// If we're looking at a CRLF pair, skip the second half
i++;
}
line = word;
} else if (b[i] == ' ') {
if (line.length() + word.length() + 1 > 78) {
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = word;
} else if (line.isEmpty()) {
line = word;
} else {
line = line + ' ' + word;
}
word = "";
} else {
word += b[i];
}
}
if (line.length() + word.length() + 1 > 78) {
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = word;
} else if (!word.isEmpty()) {
line += ' ' + word;
}
if (!line.isEmpty()) {
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
}
} else if (useQuotedPrintable) {
QByteArray b = body().toUtf8();
int ct = b.length();
QByteArray line;
for (int i = 0; i < ct; i++) {
if (b[i] == '\n' || b[i] == '\r') {
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
line = "";
if ((b[i + 1] == '\n' || b[i + 1] == '\r') && b[i] != b[i + 1]) {
// If we're looking at a CRLF pair, skip the second half
i++;
}
} else if (line.length() > 74) {
rv += line + "=\r\n";
line = "";
}
if (QXT_MUST_QP(b[i])) {
line += "=" + b.mid(i, 1).toHex().toUpper();
} else {
line += b[i];
}
}
if (!line.isEmpty()) {
if (line[0] == '.')
rv += ".";
rv += line + "\r\n";
}
} else /* base64 */
{
QByteArray b = body().toUtf8().toBase64();
int ct = b.length();
for (int i = 0; i < ct; i += 78) {
rv += b.mid(i, 78) + "\r\n";
}
}
if (attach.count()) {
for (const QString &filename : attach.keys()) {
rv += "--" + qxt_d->boundary + "\r\n";
rv +=
qxt_fold_mime_header("Content-Disposition", QDir(filename).dirName(), "attachment; filename=");
rv += attach[filename].mimeData();
}
rv += "--" + qxt_d->boundary + "--\r\n";
}
return rv;
}
+85
View File
@@ -0,0 +1,85 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTMAILMESSAGE_H
#define QXTMAILMESSAGE_H
#include "qxtglobal.h"
#include "qxtmailattachment.h"
#include <QStringList>
#include <QHash>
#include <QMetaType>
#include <QSharedDataPointer>
struct QxtMailMessagePrivate;
class QXT_NETWORK_EXPORT QxtMailMessage
{
public:
enum RecipientType
{
To,
Cc,
Bcc
};
QxtMailMessage();
QxtMailMessage(const QxtMailMessage& other);
QxtMailMessage(const QString& sender, const QString& recipient);
QxtMailMessage& operator=(const QxtMailMessage& other);
~QxtMailMessage();
QString sender() const;
void setSender(const QString&);
QString subject() const;
void setSubject(const QString&);
QString body() const;
void setBody(const QString&);
QStringList recipients(RecipientType type = To) const;
void addRecipient(const QString&, RecipientType type = To);
void removeRecipient(const QString&);
QHash<QString, QString> extraHeaders() const;
QByteArray extraHeader(const QString&) const;
bool hasExtraHeader(const QString&) const;
void setExtraHeader(const QString& key, const QString& value);
void setExtraHeaders(const QHash<QString, QString>&);
void removeExtraHeader(const QString& key);
QHash<QString, QxtMailAttachment> attachments() const;
QxtMailAttachment attachment(const QString& filename) const;
void addAttachment(const QString& filename, const QxtMailAttachment& attach);
void removeAttachment(const QString& filename);
QByteArray rfc2822() const;
private:
QSharedDataPointer<QxtMailMessagePrivate> qxt_d;
};
Q_DECLARE_TYPEINFO(QxtMailMessage, Q_MOVABLE_TYPE);
#endif // QXTMAIL_H
+536
View File
@@ -0,0 +1,536 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
/*!
* \class QxtSmtp
* \inmodule QxtNetwork
* \brief The QxtSmtp class implements the SMTP protocol for sending email
*/
#include "qxtsmtp.h"
#include "qxthmac.h"
#include "qxtsmtp_p.h"
#include <QNetworkInterface>
#include <QSslSocket>
#include <QStringList>
#include <QTcpSocket>
QxtSmtpPrivate::QxtSmtpPrivate() : QObject(0)
{
// empty ctor
}
QxtSmtp::QxtSmtp(QObject *parent) : QObject(parent)
{
QXT_INIT_PRIVATE(QxtSmtp);
qxt_d().state = QxtSmtpPrivate::Disconnected;
qxt_d().nextID = 0;
qxt_d().socket = new QSslSocket(this);
QObject::connect(socket(), SIGNAL(encrypted()), this, SIGNAL(encrypted()));
// QObject::connect(socket(), SIGNAL(encrypted()), &qxt_d(), SLOT(ehlo()));
QObject::connect(socket(), SIGNAL(connected()), this, SIGNAL(connected()));
QObject::connect(socket(), SIGNAL(disconnected()), this, SIGNAL(disconnected()));
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QObject::connect(socket(), SIGNAL(errorOccurred(QAbstractSocket::SocketError)), &qxt_d(),
SLOT(socketError(QAbstractSocket::SocketError)));
#else
QObject::connect(socket(), SIGNAL(error(QAbstractSocket::SocketError)), &qxt_d(),
SLOT(socketError(QAbstractSocket::SocketError)));
#endif
QObject::connect(this, SIGNAL(authenticated()), &qxt_d(), SLOT(sendNext()));
QObject::connect(socket(), SIGNAL(readyRead()), &qxt_d(), SLOT(socketRead()));
}
QByteArray QxtSmtp::username() const
{
return qxt_d().username;
}
void QxtSmtp::setUsername(const QByteArray &username)
{
qxt_d().username = username;
}
QByteArray QxtSmtp::password() const
{
return qxt_d().password;
}
void QxtSmtp::setPassword(const QByteArray &password)
{
qxt_d().password = password;
}
int QxtSmtp::send(const QxtMailMessage &message)
{
int messageID = ++qxt_d().nextID;
qxt_d().pending.append(qMakePair(messageID, message));
if (qxt_d().state == QxtSmtpPrivate::Waiting)
qxt_d().sendNext();
return messageID;
}
int QxtSmtp::pendingMessages() const
{
return qxt_d().pending.count();
}
QTcpSocket *QxtSmtp::socket() const
{
return qxt_d().socket;
}
void QxtSmtp::connectToHost(const QString &hostName, quint16 port)
{
qxt_d().useSecure = false;
qxt_d().state = QxtSmtpPrivate::StartState;
socket()->connectToHost(hostName, port);
}
void QxtSmtp::connectToHost(const QHostAddress &address, quint16 port)
{
connectToHost(address.toString(), port);
}
void QxtSmtp::disconnectFromHost()
{
socket()->disconnectFromHost();
}
bool QxtSmtp::startTlsDisabled() const
{
return qxt_d().disableStartTLS;
}
void QxtSmtp::setStartTlsDisabled(bool disable)
{
qxt_d().disableStartTLS = disable;
}
QSslSocket *QxtSmtp::sslSocket() const
{
return qxt_d().socket;
}
void QxtSmtp::connectToSecureHost(const QString &hostName, quint16 port)
{
qxt_d().useSecure = true;
qxt_d().state = QxtSmtpPrivate::StartState;
sslSocket()->connectToHostEncrypted(hostName, port);
}
void QxtSmtp::connectToSecureHost(const QHostAddress &address, quint16 port)
{
connectToSecureHost(address.toString(), port);
}
bool QxtSmtp::hasExtension(const QString &extension)
{
return qxt_d().extensions.contains(extension);
}
QString QxtSmtp::extensionData(const QString &extension)
{
return qxt_d().extensions[extension];
}
void QxtSmtpPrivate::socketError(QAbstractSocket::SocketError err)
{
if (err == QAbstractSocket::SslHandshakeFailedError) {
emit qxt_p().encryptionFailed();
emit qxt_p().encryptionFailed(socket->errorString().toLatin1());
} else if (state == StartState) {
emit qxt_p().connectionFailed();
emit qxt_p().connectionFailed(socket->errorString().toLatin1());
}
}
void QxtSmtpPrivate::socketRead()
{
buffer += socket->readAll();
while (true) {
int pos = buffer.indexOf("\r\n");
if (pos < 0)
return;
QByteArray line = buffer.left(pos);
buffer = buffer.mid(pos + 2);
QByteArray code = line.left(3);
switch (state) {
case StartState:
if (code[0] != '2') {
socket->disconnectFromHost();
} else {
ehlo();
}
break;
case HeloSent:
case EhloSent:
case EhloGreetReceived:
parseEhlo(code, (line[3] != ' '), line.mid(4));
break;
case StartTLSSent:
if (code == "220") {
socket->startClientEncryption();
ehlo();
} else {
authenticate();
}
break;
case AuthRequestSent:
case AuthUsernameSent:
if (authType == AuthPlain)
authPlain();
else if (authType == AuthLogin)
authLogin();
else
authCramMD5(line.mid(4));
break;
case AuthSent:
if (code[0] == '2') {
state = Authenticated;
emit qxt_p().authenticated();
} else {
state = Disconnected;
emit qxt_p().authenticationFailed();
emit qxt_p().authenticationFailed(line);
emit socket->disconnectFromHost();
}
break;
case MailToSent:
case RcptAckPending:
if (code[0] != '2') {
emit qxt_p().mailFailed(pending.first().first, code.toInt());
emit qxt_p().mailFailed(pending.first().first, code.toInt(), line);
// pending.removeFirst();
// DO NOT remove it, the body sent state needs this message to assigned the next mail failed message
// that will the sendNext a reset will be sent to clear things out
sendNext();
state = BodySent;
} else
sendNextRcpt(code, line);
break;
case SendingBody:
sendBody(code, line);
break;
case BodySent:
if (pending.count()) {
// if you removeFirst in RcpActpending/MailToSent on an error, and the queue is now empty,
// you will get into this state and then crash because no check is done. CHeck added but shouldnt
// be necessary since I commented out the removeFirst
if (code[0] != '2') {
emit qxt_p().mailFailed(pending.first().first, code.toInt());
emit qxt_p().mailFailed(pending.first().first, code.toInt(), line);
} else
emit qxt_p().mailSent(pending.first().first);
pending.removeFirst();
}
sendNext();
break;
case Resetting:
if (code[0] != '2') {
emit qxt_p().connectionFailed();
emit qxt_p().connectionFailed(line);
} else {
state = Waiting;
sendNext();
}
break;
case Disconnected:
case EhloExtensionsReceived:
case EhloDone:
case Authenticated:
case Waiting:
// only to make compiler happy
break;
}
}
}
void QxtSmtpPrivate::ehlo()
{
QByteArray address = "127.0.0.1";
for (const QHostAddress &addr : QNetworkInterface::allAddresses()) {
if (addr == QHostAddress::LocalHost || addr == QHostAddress::LocalHostIPv6)
continue;
address = addr.toString().toLatin1();
break;
}
socket->write("ehlo " + address + "\r\n");
extensions.clear();
state = EhloSent;
}
void QxtSmtpPrivate::parseEhlo(const QByteArray &code, bool cont, const QString &line)
{
if (code != "250") {
// error!
if (state != HeloSent) {
// maybe let's try HELO
socket->write("helo\r\n");
state = HeloSent;
} else {
// nope
socket->write("QUIT\r\n");
socket->flush();
socket->disconnectFromHost();
}
return;
} else if (state != EhloGreetReceived) {
if (!cont) {
// greeting only, no extensions
state = EhloDone;
} else {
// greeting followed by extensions
state = EhloGreetReceived;
return;
}
} else {
extensions[line.section(' ', 0, 0).toUpper()] = line.section(' ', 1);
if (!cont)
state = EhloDone;
}
if (state != EhloDone)
return;
if (extensions.contains("STARTTLS") && !disableStartTLS) {
startTLS();
} else {
authenticate();
}
}
void QxtSmtpPrivate::startTLS()
{
socket->write("starttls\r\n");
state = StartTLSSent;
}
void QxtSmtpPrivate::authenticate()
{
if (!extensions.contains("AUTH") || username.isEmpty() || password.isEmpty()) {
state = Authenticated;
emit qxt_p().authenticated();
} else {
QStringList auth = extensions["AUTH"].toUpper().split(' ', Qt::SkipEmptyParts);
if (auth.contains("CRAM-MD5")) {
authCramMD5();
} else if (auth.contains("PLAIN")) {
authPlain();
} else if (auth.contains("LOGIN")) {
authLogin();
} else {
state = Authenticated;
emit qxt_p().authenticated();
}
}
}
void QxtSmtpPrivate::authCramMD5(const QByteArray &challenge)
{
if (state != AuthRequestSent) {
socket->write("auth cram-md5\r\n");
authType = AuthCramMD5;
state = AuthRequestSent;
} else {
QxtHmac hmac(QCryptographicHash::Md5);
hmac.setKey(password);
hmac.addData(QByteArray::fromBase64(challenge));
QByteArray response = username + ' ' + hmac.result().toHex();
socket->write(response.toBase64() + "\r\n");
state = AuthSent;
}
}
void QxtSmtpPrivate::authPlain()
{
if (state != AuthRequestSent) {
socket->write("auth plain\r\n");
authType = AuthPlain;
state = AuthRequestSent;
} else {
QByteArray auth;
auth += '\0';
auth += username;
auth += '\0';
auth += password;
socket->write(auth.toBase64() + "\r\n");
state = AuthSent;
}
}
void QxtSmtpPrivate::authLogin()
{
if (state != AuthRequestSent && state != AuthUsernameSent) {
socket->write("auth login\r\n");
authType = AuthLogin;
state = AuthRequestSent;
} else if (state == AuthRequestSent) {
socket->write(username.toBase64() + "\r\n");
state = AuthUsernameSent;
} else {
socket->write(password.toBase64() + "\r\n");
state = AuthSent;
}
}
static QByteArray qxt_extract_address(const QString &address)
{
int parenDepth = 0;
int addrStart = -1;
bool inQuote = false;
int ct = address.length();
for (int i = 0; i < ct; i++) {
QChar ch = address[i];
if (inQuote) {
if (ch == '"')
inQuote = false;
} else if (addrStart != -1) {
if (ch == '>')
return address.mid(addrStart, (i - addrStart)).toLatin1();
} else if (ch == '(') {
parenDepth++;
} else if (ch == ')') {
parenDepth--;
if (parenDepth < 0)
parenDepth = 0;
} else if (ch == '"') {
if (parenDepth == 0)
inQuote = true;
} else if (ch == '<') {
if (!inQuote && parenDepth == 0)
addrStart = i + 1;
}
}
return address.toLatin1();
}
void QxtSmtpPrivate::sendNext()
{
if (state == Disconnected) {
// leave the mail in the queue if not ready to send
return;
}
if (pending.isEmpty()) {
// if there are no additional mails to send, finish up
state = Waiting;
emit qxt_p().finished();
return;
}
if (state != Waiting) {
state = Resetting;
socket->write("rset\r\n");
return;
}
const QxtMailMessage &msg = pending.first().second;
rcptNumber = rcptAck = mailAck = 0;
recipients =
msg.recipients(QxtMailMessage::To) + msg.recipients(QxtMailMessage::Cc) + msg.recipients(QxtMailMessage::Bcc);
if (recipients.count() == 0) {
// can't send an e-mail with no recipients
emit qxt_p().mailFailed(pending.first().first, QxtSmtp::NoRecipients);
emit qxt_p().mailFailed(pending.first().first, QxtSmtp::NoRecipients, QByteArray("e-mail has no recipients"));
pending.removeFirst();
sendNext();
return;
}
// We explicitly use lowercase keywords because for some reason gmail
// interprets any string starting with an uppercase R as a request
// to renegotiate the SSL connection.
socket->write("mail from:<" + qxt_extract_address(msg.sender()) + ">\r\n");
if (extensions.contains("PIPELINING")) // almost all do nowadays
{
for (const QString &rcpt : recipients) {
socket->write("rcpt to:<" + qxt_extract_address(rcpt) + ">\r\n");
}
state = RcptAckPending;
} else {
state = MailToSent;
}
}
void QxtSmtpPrivate::sendNextRcpt(const QByteArray &code, const QByteArray &line)
{
int messageID = pending.first().first;
const QxtMailMessage &msg = pending.first().second;
if (code[0] != '2') {
// on failure, emit a warning signal
if (!mailAck) {
emit qxt_p().senderRejected(messageID, msg.sender());
emit qxt_p().senderRejected(messageID, msg.sender(), line);
} else {
emit qxt_p().recipientRejected(messageID, msg.sender());
emit qxt_p().recipientRejected(messageID, msg.sender(), line);
}
} else if (!mailAck) {
mailAck = true;
} else {
rcptAck++;
}
if (rcptNumber == recipients.count()) {
// all recipients have been sent
if (rcptAck == 0) {
// no recipients were considered valid
emit qxt_p().mailFailed(messageID, code.toInt());
emit qxt_p().mailFailed(messageID, code.toInt(), line);
pending.removeFirst();
sendNext();
} else {
// at least one recipient was acknowledged, send mail body
socket->write("data\r\n");
state = SendingBody;
}
} else if (state != RcptAckPending) {
// send the next recipient unless we're only waiting on acks
socket->write("rcpt to:<" + qxt_extract_address(recipients[rcptNumber]) + ">\r\n");
rcptNumber++;
} else {
// If we're only waiting on acks, just count them
rcptNumber++;
}
}
void QxtSmtpPrivate::sendBody(const QByteArray &code, const QByteArray &line)
{
int messageID = pending.first().first;
const QxtMailMessage &msg = pending.first().second;
if (code[0] != '3') {
emit qxt_p().mailFailed(messageID, code.toInt());
emit qxt_p().mailFailed(messageID, code.toInt(), line);
pending.removeFirst();
sendNext();
return;
}
socket->write(msg.rfc2822());
socket->write(".\r\n");
state = BodySent;
}
+111
View File
@@ -0,0 +1,111 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTSMTP_H
#define QXTSMTP_H
#include <QObject>
#include <QHostAddress>
#include <QString>
#include "qxtglobal.h"
#include "qxtmailmessage.h"
class QTcpSocket;
class QSslSocket;
class QxtSmtpPrivate;
class QXT_NETWORK_EXPORT QxtSmtp : public QObject
{
Q_OBJECT
public:
enum SmtpError
{
NoError,
NoRecipients,
CommandUnrecognized = 500,
SyntaxError,
CommandNotImplemented,
BadSequence,
ParameterNotImplemented,
MailboxUnavailable = 550,
UserNotLocal,
MessageTooLarge,
InvalidMailboxName,
TransactionFailed
};
QxtSmtp(QObject* parent = 0);
QByteArray username() const;
void setUsername(const QByteArray& name);
QByteArray password() const;
void setPassword(const QByteArray& password);
int send(const QxtMailMessage& message);
int pendingMessages() const;
QTcpSocket* socket() const;
void connectToHost(const QString& hostName, quint16 port = 25);
void connectToHost(const QHostAddress& address, quint16 port = 25);
void disconnectFromHost();
bool startTlsDisabled() const;
void setStartTlsDisabled(bool disable);
QSslSocket* sslSocket() const;
void connectToSecureHost(const QString& hostName, quint16 port = 465);
void connectToSecureHost(const QHostAddress& address, quint16 port = 465);
bool hasExtension(const QString& extension);
QString extensionData(const QString& extension);
Q_SIGNALS:
void connected();
void connectionFailed();
void connectionFailed( const QByteArray & msg );
void encrypted();
void encryptionFailed();
void encryptionFailed( const QByteArray & msg );
void authenticated();
void authenticationFailed();
void authenticationFailed( const QByteArray & msg );
void senderRejected(int mailID, const QString& address );
void senderRejected(int mailID, const QString& address, const QByteArray & msg );
void recipientRejected(int mailID, const QString& address );
void recipientRejected(int mailID, const QString& address, const QByteArray & msg );
void mailFailed(int mailID, int errorCode);
void mailFailed(int mailID, int errorCode, const QByteArray & msg);
void mailSent(int mailID);
void finished();
void disconnected();
private:
QXT_DECLARE_PRIVATE(QxtSmtp)
};
#endif // QXTSMTP_H
+102
View File
@@ -0,0 +1,102 @@
/****************************************************************************
**
** Copyright (C) Qxt Foundation. Some rights reserved.
**
** This file is part of the QxtWeb module of the Qxt library.
**
** This library is free software; you can redistribute it and/or modify it
** under the terms of the Common Public License, version 1.0, as published
** by IBM, and/or under the terms of the GNU Lesser General Public License,
** version 2.1, as published by the Free Software Foundation.
**
** This file is provided "AS IS", without WARRANTIES OR CONDITIONS OF ANY
** KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
** WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR
** FITNESS FOR A PARTICULAR PURPOSE.
**
** You should have received a copy of the CPL and the LGPL along with this
** file. See the LICENSE file and the cpl1.0.txt/lgpl-2.1.txt files
** included with the source distribution for more information.
** If you did not receive a copy of the licenses, contact the Qxt Foundation.
**
** <http://libqxt.org> <foundation@libqxt.org>
**
****************************************************************************/
#ifndef QXTSMTP_P_H
#define QXTSMTP_P_H
#include "qxtsmtp.h"
#include <QHash>
#include <QString>
#include <QList>
#include <QPair>
class QxtSmtpPrivate : public QObject, public QxtPrivate<QxtSmtp>
{
Q_OBJECT
public:
QxtSmtpPrivate();
QXT_DECLARE_PUBLIC(QxtSmtp)
enum SmtpState
{
Disconnected,
StartState,
EhloSent,
EhloGreetReceived,
EhloExtensionsReceived,
EhloDone,
HeloSent,
StartTLSSent,
AuthRequestSent,
AuthUsernameSent,
AuthSent,
Authenticated,
MailToSent,
RcptAckPending,
SendingBody,
BodySent,
Waiting,
Resetting
};
enum AuthType
{
AuthPlain,
AuthLogin,
AuthCramMD5
};
bool useSecure, disableStartTLS;
SmtpState state;// rather then an int use the enum. makes sure invalid states are entered at compile time, and makes debugging easier
AuthType authType;
QByteArray buffer, username, password;
QHash<QString, QString> extensions;
QList<QPair<int, QxtMailMessage> > pending;
QStringList recipients;
int nextID, rcptNumber, rcptAck;
bool mailAck;
QSslSocket* socket;
void parseEhlo(const QByteArray& code, bool cont, const QString& line);
void startTLS();
void authenticate();
void authCramMD5(const QByteArray& challenge = QByteArray());
void authPlain();
void authLogin();
void sendNextRcpt(const QByteArray& code, const QByteArray & line);
void sendBody(const QByteArray& code, const QByteArray & line);
public slots:
void socketError(QAbstractSocket::SocketError err);
void socketRead();
void ehlo();
void sendNext();
};
#endif // QXTSMTP_P_H
+217
View File
@@ -0,0 +1,217 @@
#include "smtpclient.h"
#include "settingscache.h"
#include "smtp/qxtsmtp.h"
#include <QSslSocket>
#include <QTcpSocket>
SmtpClient::SmtpClient(QObject *parent) : QObject(parent)
{
smtp = new QxtSmtp(this);
connect(smtp, SIGNAL(authenticated()), this, SLOT(authenticated()));
connect(smtp, SIGNAL(authenticationFailed(const QByteArray &)), this,
SLOT(authenticationFailed(const QByteArray &)));
connect(smtp, SIGNAL(connected()), this, SLOT(connected()));
connect(smtp, SIGNAL(connectionFailed(const QByteArray &)), this, SLOT(connectionFailed(const QByteArray &)));
connect(smtp, SIGNAL(disconnected()), this, SLOT(disconnected()));
connect(smtp, SIGNAL(encrypted()), this, SLOT(encrypted()));
connect(smtp, SIGNAL(encryptionFailed(const QByteArray &)), this, SLOT(encryptionFailed(const QByteArray &)));
connect(smtp, SIGNAL(finished()), this, SLOT(finished()));
connect(smtp, SIGNAL(mailFailed(int, int, const QByteArray &)), this,
SLOT(mailFailed(int, int, const QByteArray &)));
connect(smtp, SIGNAL(mailSent(int)), this, SLOT(mailSent(int)));
connect(smtp, SIGNAL(recipientRejected(int, const QString &, const QByteArray &)), this,
SLOT(recipientRejected(int, const QString &, const QByteArray &)));
connect(smtp, SIGNAL(senderRejected(int, const QString &, const QByteArray &)), this,
SLOT(senderRejected(int, const QString &, const QByteArray &)));
}
SmtpClient::~SmtpClient()
{
if (smtp) {
delete smtp;
smtp = 0;
}
}
bool SmtpClient::enqueueActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token)
{
QString email = settingsCache->value("smtp/email", "").toString();
QString name = settingsCache->value("smtp/name", "").toString();
QString subject = settingsCache->value("smtp/subject", "").toString();
QString body = settingsCache->value("smtp/body", "").toString();
if (email.isEmpty()) {
qDebug() << "[MAIL] Missing sender email in configuration";
return false;
}
if (subject.isEmpty()) {
qDebug() << "[MAIL] Missing subject field in configuration";
return false;
}
if (body.isEmpty()) {
qDebug() << "[MAIL] Missing body field in configuration";
return false;
}
if (recipient.isEmpty()) {
qDebug() << "[MAIL] Missing recipient field for user " << nickname;
return false;
}
if (token.isEmpty()) {
qDebug() << "[MAIL] Missing token field for user " << nickname;
return false;
}
QxtMailMessage message;
message.setSender(name + " <" + email + ">");
message.addRecipient(recipient);
message.setSubject(subject);
message.setBody(body.replace("%username", nickname).replace("%token", token));
int id = smtp->send(message);
qDebug() << "[MAIL] Enqueued mail to" << recipient << "as" << id;
return true;
}
bool SmtpClient::enqueueForgotPasswordTokenMail(const QString &nickname, const QString &recipient, const QString &token)
{
QString email = settingsCache->value("smtp/email", "").toString();
QString name = settingsCache->value("smtp/name", "").toString();
QString subject = settingsCache->value("forgotpassword/subject", "").toString();
QString body = settingsCache->value("forgotpassword/body", "").toString();
if (email.isEmpty()) {
qDebug() << "[MAIL] Missing sender email in configuration";
return false;
}
if (subject.isEmpty()) {
qDebug() << "[MAIL] Missing subject field in configuration";
return false;
}
if (body.isEmpty()) {
qDebug() << "[MAIL] Missing body field in configuration";
return false;
}
if (recipient.isEmpty()) {
qDebug() << "[MAIL] Missing recipient field for user " << nickname;
return false;
}
if (token.isEmpty()) {
qDebug() << "[MAIL] Missing token field for user " << nickname;
return false;
}
QxtMailMessage message;
message.setSender(name + " <" + email + ">");
message.addRecipient(recipient);
message.setSubject(subject);
message.setBody(body.replace("%username", nickname).replace("%token", token));
int id = smtp->send(message);
qDebug() << "[MAIL] Enqueued mail to" << recipient << "as" << id;
return true;
}
void SmtpClient::sendAllEmails()
{
// still connected from the previous round
if (smtp->socket()->state() == QAbstractSocket::ConnectedState) {
return;
}
if (smtp->pendingMessages() == 0) {
return;
}
QString connectionType = settingsCache->value("smtp/connection", "tcp").toString();
QString host = settingsCache->value("smtp/host", "localhost").toString();
int port = settingsCache->value("smtp/port", 25).toInt();
QByteArray username = settingsCache->value("smtp/username", "").toByteArray();
QByteArray password = settingsCache->value("smtp/password", "").toByteArray();
bool acceptAllCerts = settingsCache->value("smtp/acceptallcerts", false).toBool();
smtp->setUsername(username);
smtp->setPassword(password);
// Connect
if (connectionType == "ssl") {
if (acceptAllCerts) {
smtp->sslSocket()->setPeerVerifyMode(QSslSocket::QueryPeer);
}
smtp->connectToSecureHost(host, port);
} else {
smtp->connectToHost(host, port);
}
}
void SmtpClient::authenticated()
{
qDebug() << "[MAIL] authenticated";
}
void SmtpClient::authenticationFailed(const QByteArray &msg)
{
qDebug() << "[MAIL] authenticationFailed" << QString(msg);
}
void SmtpClient::connected()
{
qDebug() << "[MAIL] connected";
}
void SmtpClient::connectionFailed(const QByteArray &msg)
{
qDebug() << "[MAIL] connectionFailed" << QString(msg);
}
void SmtpClient::disconnected()
{
qDebug() << "[MAIL] disconnected";
}
void SmtpClient::encrypted()
{
qDebug() << "[MAIL] encrypted";
}
void SmtpClient::encryptionFailed(const QByteArray &msg)
{
qDebug() << "[MAIL] encryptionFailed" << QString(msg);
qDebug() << "[MAIL] Try enabling the \"acceptallcerts\" option in servatrice.ini";
}
void SmtpClient::finished()
{
qDebug() << "[MAIL] finished";
smtp->disconnectFromHost();
}
void SmtpClient::mailFailed(int mailID, int errorCode, const QByteArray &msg)
{
qDebug() << "[MAIL] mailFailed id=" << mailID << " errorCode=" << errorCode << "msg=" << QString(msg);
}
void SmtpClient::mailSent(int mailID)
{
qDebug() << "[MAIL] mailSent" << mailID;
}
void SmtpClient::recipientRejected(int mailID, const QString &address, const QByteArray &msg)
{
qDebug() << "[MAIL] recipientRejected id=" << mailID << " address=" << address << "msg=" << QString(msg);
}
void SmtpClient::senderRejected(int mailID, const QString &address, const QByteArray &msg)
{
qDebug() << "[MAIL] senderRejected id=" << mailID << " address=" << address << "msg=" << QString(msg);
}
+37
View File
@@ -0,0 +1,37 @@
#ifndef SMTPCLIENT_H
#define SMTPCLIENT_H
#include <QObject>
class QxtSmtp;
class QxtMailMessage;
class SmtpClient : public QObject
{
Q_OBJECT
public:
SmtpClient(QObject *parent = 0);
~SmtpClient();
protected:
QxtSmtp *smtp;
public slots:
bool enqueueActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token);
bool enqueueForgotPasswordTokenMail(const QString &nickname, const QString &recipient, const QString &token);
void sendAllEmails();
protected slots:
void authenticated();
void authenticationFailed(const QByteArray &msg);
void connected();
void connectionFailed(const QByteArray &msg);
void disconnected();
void encrypted();
void encryptionFailed(const QByteArray &msg);
void finished();
void mailFailed(int mailID, int errorCode, const QByteArray &msg);
void mailSent(int mailID);
void recipientRejected(int mailID, const QString &address, const QByteArray &msg);
void senderRejected(int mailID, const QString &address, const QByteArray &msg);
};
#endif