Initial commit of mtgonline project
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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:;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
|
||||
@@ -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 ®ExpStr : 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user