35 #include <QCryptographicHash>
39 #include <QStringList>
58 QByteArray
hmacMD5(QByteArray key,
const QByteArray &msg)
60 const int blockSize = 64;
61 if (key.length() > blockSize)
62 key = QCryptographicHash::hash(key, QCryptographicHash::Md5);
64 QByteArray innerPadding(blockSize,
char(0x36));
65 QByteArray outerPadding(blockSize,
char(0x5c));
69 for (
int i = 0; i < key.length(); ++i)
71 innerPadding[i] = innerPadding[i] ^ key.at(i);
72 outerPadding[i] = outerPadding[i] ^ key.at(i);
76 QByteArray total = outerPadding;
77 QByteArray part = innerPadding;
79 total.append(QCryptographicHash::hash(part, QCryptographicHash::Md5));
80 return QCryptographicHash::hash(total, QCryptographicHash::Md5);
85 QString hostname = QHostInfo::localHostName();
86 if (hostname.isEmpty())
87 hostname =
"localhost";
89 return hostname.toLocal8Bit();
94 return std::none_of(
string.cbegin(),
string.cend(), [](
const QChar &ch)
96 return ch > QChar(0xff);
107 , m_authType(AuthPlain)
109 static bool needToRegisterMetaType =
true;
111 if (needToRegisterMetaType)
113 qRegisterMetaType<QAbstractSocket::SocketError>();
114 needToRegisterMetaType =
false;
117 #ifndef QT_NO_OPENSSL
124 connect(
m_socket, &QAbstractSocket::disconnected,
this, &QObject::deleteLater);
128 Q_ASSERT(
hmacMD5(
"Jefe",
"what do ya want for nothing?").toHex()
129 ==
"750c783e6ab0b503eaa86e310a5db738");
130 Q_ASSERT(
hmacMD5(QByteArray::fromHex(
"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
"Hi There").toHex()
131 ==
"9294727a3638bb1c13f48ef8158bfc9d");
136 qDebug() << Q_FUNC_INFO;
139 void Smtp::sendMail(
const QString &from,
const QString &to,
const QString &subject,
const QString &body)
146 +
"MIME-Version: 1.0\r\n"
147 +
"Content-Type: text/plain; charset=UTF-8\r\n"
148 +
"Content-Transfer-Encoding: base64\r\n"
151 QString crlfBody = body;
152 const QByteArray b = crlfBody.replace(
"\n",
"\r\n").toUtf8().toBase64();
153 const int ct = b.length();
154 for (
int i = 0; i < ct; i += 78)
166 #ifndef QT_NO_OPENSSL
177 #ifndef QT_NO_OPENSSL
184 qDebug() << Q_FUNC_INFO;
189 const int pos =
m_buffer.indexOf(
"\r\n");
191 const QByteArray line =
m_buffer.left(pos);
193 qDebug() <<
"Response line:" << line;
195 const QByteArray code = line.left(3);
211 logError(QLatin1String(
"Connection failed, unrecognized reply: ") + line);
220 #ifndef QT_NO_OPENSSL
243 qDebug() <<
"Sending <mail from>...";
251 logError(QLatin1String(
"Authentication failed, msg: ") + line);
264 logError(QLatin1String(
"<mail from> was rejected by server, msg: ") + line);
277 logError(QLatin1String(
"<Rcpt to> was rejected by server, msg: ") + line);
290 logError(QLatin1String(
"<data> was rejected by server, msg: ") + line);
304 logError(QLatin1String(
"Message was rejected by the server, error: ") + line);
309 qDebug() <<
"Disconnecting from host";
319 QByteArray line = key.toLatin1() +
": ";
320 if (!prefix.isEmpty()) line += prefix;
323 bool firstWord =
true;
324 for (
const QByteArray &word :
asConst(
value.toLatin1().split(
' ')))
326 if (line.size() > 78)
328 rv = rv + line +
"\r\n";
342 const QByteArray utf8 =
value.toUtf8();
344 const QByteArray base64 = utf8.toBase64();
345 const int ct = base64.length();
346 line +=
"=?utf-8?b?";
347 for (
int i = 0; i < ct; i += 4)
354 line = line + base64.mid(i, 4);
358 return rv + line +
"\r\n";
364 m_socket->write(
"ehlo " + address +
"\r\n");
372 m_socket->write(
"helo " + address +
"\r\n");
385 qDebug() <<
"EHLO failed, trying HELO instead...";
392 logError(
"Both EHLO and HELO failed, msg: " + line);
403 qDebug() <<
"No extension";
410 qDebug() <<
"EHLO greet received";
416 qDebug() << Q_FUNC_INFO <<
"Supported extension: " << line.section(
' ', 0, 0).toUpper()
417 << line.section(
' ', 1);
418 m_extensions[line.section(
' ', 0, 0).toUpper()] = line.section(
' ', 1);
427 qDebug() <<
"STARTTLS";
438 qDebug() << Q_FUNC_INFO;
443 qDebug() <<
"Skipping authentication...";
448 m_buffer.push_front(
"250 QBT FAKE RESPONSE\r\n");
454 const QStringList auth =
m_extensions[
"AUTH"].toUpper().split(
' ', Qt::SkipEmptyParts);
455 if (auth.contains(
"CRAM-MD5"))
457 qDebug() <<
"Using CRAM-MD5 authentication...";
460 else if (auth.contains(
"PLAIN"))
462 qDebug() <<
"Using PLAIN authentication...";
465 else if (auth.contains(
"LOGIN"))
467 qDebug() <<
"Using LOGIN authentication...";
473 logError(
"The SMTP server does not seem to support any of the authentications modes "
474 "we support [CRAM-MD5|PLAIN|LOGIN], skipping authentication, "
475 "knowing it is likely to fail... Server Auth Modes: " + auth.join(
'|'));
480 m_buffer.push_front(
"250 QBT FAKE RESPONSE\r\n");
486 qDebug() << Q_FUNC_INFO;
487 #ifndef QT_NO_OPENSSL
500 m_socket->write(
"auth cram-md5\r\n");
507 const QByteArray response =
m_username.toLatin1() +
' '
509 m_socket->write(response.toBase64() +
"\r\n");
524 qDebug() <<
"username: " <<
m_username.toLatin1();
527 qDebug() <<
"password: " <<
m_password.toLatin1();
529 m_socket->write(
"auth plain " + auth.toBase64() +
"\r\n");
560 qDebug() <<
"Email Notification Error:" << msg;
567 const QDateTime nowDateTime = QDateTime::currentDateTime();
568 const QDate nowDate = nowDateTime.date();
569 const QLocale eng(QLocale::English);
571 const QString timeStr = nowDateTime.time().toString(
"HH:mm:ss");
572 const QString weekDayStr = eng.dayName(nowDate.dayOfWeek(), QLocale::ShortFormat);
573 const QString dayStr = QString::number(nowDate.day());
574 const QString monthStr = eng.monthName(nowDate.month(), QLocale::ShortFormat);
575 const QString yearStr = QString::number(nowDate.year());
577 QDateTime tmp = nowDateTime;
578 tmp.setTimeSpec(Qt::UTC);
579 const int timeOffsetHour = nowDateTime.secsTo(tmp) / 3600;
580 const int timeOffsetMin = nowDateTime.secsTo(tmp) / 60 - (60 * timeOffsetHour);
581 const int timeOffset = timeOffsetHour * 100 + timeOffsetMin;
584 std::snprintf(buf,
sizeof(buf),
"%+05d", timeOffset);
585 const QString timeOffsetStr = buf;
587 const QString ret = weekDayStr +
", " + dayStr +
' ' + monthStr +
' ' + yearStr +
' ' + timeStr +
' ' + timeOffsetStr;
595 if (socketError != QAbstractSocket::RemoteHostClosedError)
void addMessage(const QString &message, const Log::MsgType &type=Log::NORMAL)
static Logger * instance()
Smtp(QObject *parent=nullptr)
QHash< QString, QString > m_extensions
void logError(const QString &msg)
QString getCurrentDateTime() const
void authCramMD5(const QByteArray &challenge={})
void parseEhloResponse(const QByteArray &code, bool continued, const QString &line)
void error(QAbstractSocket::SocketError socketError)
QByteArray encodeMimeHeader(const QString &key, const QString &value, const QByteArray &prefix={})
void sendMail(const QString &from, const QString &to, const QString &subject, const QString &body)
QString getMailNotificationSMTPUsername() const
QString getMailNotificationSMTPPassword() const
static Preferences * instance()
bool getMailNotificationSMTPAuth() const
bool getMailNotificationSMTPSSL() const
QString getMailNotificationSMTP() const
constexpr std::add_const_t< T > & asConst(T &t) noexcept
T value(const QString &key, const T &defaultValue={})
QByteArray hmacMD5(QByteArray key, const QByteArray &msg)
bool canEncodeAsLatin1(const QStringView string)
const short DEFAULT_PORT_SSL
QByteArray determineFQDN()