diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 7d0abade..99f2e62e 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -12,6 +12,7 @@ SET(cockatrice_SOURCES src/dlg_connect.cpp src/dlg_create_token.cpp src/dlg_edit_tokens.cpp + src/dlg_register.cpp src/abstractclient.cpp src/remoteclient.cpp src/main.cpp diff --git a/cockatrice/src/abstractclient.h b/cockatrice/src/abstractclient.h index 6f3b7ede..df029a1e 100644 --- a/cockatrice/src/abstractclient.h +++ b/cockatrice/src/abstractclient.h @@ -29,9 +29,10 @@ enum ClientStatus { StatusDisconnected, StatusDisconnecting, StatusConnecting, - StatusAwaitingWelcome, + StatusRegistering, + StatusActivating, StatusLoggingIn, - StatusLoggedIn + StatusLoggedIn, }; class AbstractClient : public QObject { @@ -59,6 +60,9 @@ signals: void buddyListReceived(const QList &buddyList); void ignoreListReceived(const QList &ignoreList); void replayAddedEventReceived(const Event_ReplayAdded &event); + void registerAccepted(); + void registerAcceptedNeedsActivate(); + void activateAccepted(); void sigQueuePendingCommand(PendingCommand *pend); private: @@ -71,7 +75,8 @@ protected slots: void processProtocolItem(const ServerMessage &item); protected: QMap pendingCommands; - QString userName, password; + QString userName, password, email, country, realName, token; + int gender; void setStatus(ClientStatus _status); int getNewCmdId() { return nextCmdId++; } virtual void sendCommandContainer(const CommandContainer &cont) = 0; diff --git a/cockatrice/src/dlg_register.cpp b/cockatrice/src/dlg_register.cpp new file mode 100644 index 00000000..af30da59 --- /dev/null +++ b/cockatrice/src/dlg_register.cpp @@ -0,0 +1,355 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "dlg_register.h" +#include "pb/serverinfo_user.pb.h" + +DlgRegister::DlgRegister(QWidget *parent) + : QDialog(parent) +{ + QSettings settings; + settings.beginGroup("server"); + + hostLabel = new QLabel(tr("&Host:")); + hostEdit = new QLineEdit(settings.value("hostname", "cockatrice.woogerworks.com").toString()); + hostLabel->setBuddy(hostEdit); + + portLabel = new QLabel(tr("&Port:")); + portEdit = new QLineEdit(settings.value("port", "4747").toString()); + portLabel->setBuddy(portEdit); + + playernameLabel = new QLabel(tr("Player &name:")); + playernameEdit = new QLineEdit(settings.value("playername", "Player").toString()); + playernameLabel->setBuddy(playernameEdit); + + passwordLabel = new QLabel(tr("P&assword:")); + passwordEdit = new QLineEdit(settings.value("password").toString()); + passwordLabel->setBuddy(passwordEdit); + passwordEdit->setEchoMode(QLineEdit::Password); + + emailLabel = new QLabel(tr("Email:")); + emailEdit = new QLineEdit(); + emailLabel->setBuddy(emailEdit); + + genderLabel = new QLabel(tr("Gender:")); + genderEdit = new QComboBox(); + genderLabel->setBuddy(genderEdit); + genderEdit->insertItem(0, QIcon(":/resources/genders/unknown.svg"), tr("Undefined")); + genderEdit->insertItem(1, QIcon(":/resources/genders/male.svg"), tr("Male")); + genderEdit->insertItem(2, QIcon(":/resources/genders/female.svg"), tr("Female")); + genderEdit->setCurrentIndex(0); + + countryLabel = new QLabel(tr("Country:")); + countryEdit = new QComboBox(); + countryLabel->setBuddy(countryEdit); + countryEdit->insertItem(0, tr("Undefined")); + countryEdit->addItem(QPixmap(":/resources/countries/ad.svg"), "ad"); + countryEdit->addItem(QIcon(":/resources/countries/ae.svg"), "ae"); + countryEdit->addItem(QIcon(":/resources/countries/af.svg"), "af"); + countryEdit->addItem(QIcon(":/resources/countries/ag.svg"), "ag"); + countryEdit->addItem(QIcon(":/resources/countries/ai.svg"), "ai"); + countryEdit->addItem(QIcon(":/resources/countries/al.svg"), "al"); + countryEdit->addItem(QIcon(":/resources/countries/am.svg"), "am"); + countryEdit->addItem(QIcon(":/resources/countries/ao.svg"), "ao"); + countryEdit->addItem(QIcon(":/resources/countries/aq.svg"), "aq"); + countryEdit->addItem(QIcon(":/resources/countries/ar.svg"), "ar"); + countryEdit->addItem(QIcon(":/resources/countries/as.svg"), "as"); + countryEdit->addItem(QIcon(":/resources/countries/at.svg"), "at"); + countryEdit->addItem(QIcon(":/resources/countries/au.svg"), "au"); + countryEdit->addItem(QIcon(":/resources/countries/aw.svg"), "aw"); + countryEdit->addItem(QIcon(":/resources/countries/ax.svg"), "ax"); + countryEdit->addItem(QIcon(":/resources/countries/az.svg"), "az"); + countryEdit->addItem(QIcon(":/resources/countries/ba.svg"), "ba"); + countryEdit->addItem(QIcon(":/resources/countries/bb.svg"), "bb"); + countryEdit->addItem(QIcon(":/resources/countries/bd.svg"), "bd"); + countryEdit->addItem(QIcon(":/resources/countries/be.svg"), "be"); + countryEdit->addItem(QIcon(":/resources/countries/bf.svg"), "bf"); + countryEdit->addItem(QIcon(":/resources/countries/bg.svg"), "bg"); + countryEdit->addItem(QIcon(":/resources/countries/bh.svg"), "bh"); + countryEdit->addItem(QIcon(":/resources/countries/bi.svg"), "bi"); + countryEdit->addItem(QIcon(":/resources/countries/bj.svg"), "bj"); + countryEdit->addItem(QIcon(":/resources/countries/bl.svg"), "bl"); + countryEdit->addItem(QIcon(":/resources/countries/bm.svg"), "bm"); + countryEdit->addItem(QIcon(":/resources/countries/bn.svg"), "bn"); + countryEdit->addItem(QIcon(":/resources/countries/bo.svg"), "bo"); + countryEdit->addItem(QIcon(":/resources/countries/bq.svg"), "bq"); + countryEdit->addItem(QIcon(":/resources/countries/br.svg"), "br"); + countryEdit->addItem(QIcon(":/resources/countries/bs.svg"), "bs"); + countryEdit->addItem(QIcon(":/resources/countries/bt.svg"), "bt"); + countryEdit->addItem(QIcon(":/resources/countries/bv.svg"), "bv"); + countryEdit->addItem(QIcon(":/resources/countries/bw.svg"), "bw"); + countryEdit->addItem(QIcon(":/resources/countries/by.svg"), "by"); + countryEdit->addItem(QIcon(":/resources/countries/bz.svg"), "bz"); + countryEdit->addItem(QIcon(":/resources/countries/ca.svg"), "ca"); + countryEdit->addItem(QIcon(":/resources/countries/cc.svg"), "cc"); + countryEdit->addItem(QIcon(":/resources/countries/cd.svg"), "cd"); + countryEdit->addItem(QIcon(":/resources/countries/cf.svg"), "cf"); + countryEdit->addItem(QIcon(":/resources/countries/cg.svg"), "cg"); + countryEdit->addItem(QIcon(":/resources/countries/ch.svg"), "ch"); + countryEdit->addItem(QIcon(":/resources/countries/ci.svg"), "ci"); + countryEdit->addItem(QIcon(":/resources/countries/ck.svg"), "ck"); + countryEdit->addItem(QIcon(":/resources/countries/cl.svg"), "cl"); + countryEdit->addItem(QIcon(":/resources/countries/cm.svg"), "cm"); + countryEdit->addItem(QIcon(":/resources/countries/cn.svg"), "cn"); + countryEdit->addItem(QIcon(":/resources/countries/co.svg"), "co"); + countryEdit->addItem(QIcon(":/resources/countries/cr.svg"), "cr"); + countryEdit->addItem(QIcon(":/resources/countries/cu.svg"), "cu"); + countryEdit->addItem(QIcon(":/resources/countries/cv.svg"), "cv"); + countryEdit->addItem(QIcon(":/resources/countries/cw.svg"), "cw"); + countryEdit->addItem(QIcon(":/resources/countries/cx.svg"), "cx"); + countryEdit->addItem(QIcon(":/resources/countries/cy.svg"), "cy"); + countryEdit->addItem(QIcon(":/resources/countries/cz.svg"), "cz"); + countryEdit->addItem(QIcon(":/resources/countries/de.svg"), "de"); + countryEdit->addItem(QIcon(":/resources/countries/dj.svg"), "dj"); + countryEdit->addItem(QIcon(":/resources/countries/dk.svg"), "dk"); + countryEdit->addItem(QIcon(":/resources/countries/dm.svg"), "dm"); + countryEdit->addItem(QIcon(":/resources/countries/do.svg"), "do"); + countryEdit->addItem(QIcon(":/resources/countries/dz.svg"), "dz"); + countryEdit->addItem(QIcon(":/resources/countries/ec.svg"), "ec"); + countryEdit->addItem(QIcon(":/resources/countries/ee.svg"), "ee"); + countryEdit->addItem(QIcon(":/resources/countries/eg.svg"), "eg"); + countryEdit->addItem(QIcon(":/resources/countries/eh.svg"), "eh"); + countryEdit->addItem(QIcon(":/resources/countries/er.svg"), "er"); + countryEdit->addItem(QIcon(":/resources/countries/es.svg"), "es"); + countryEdit->addItem(QIcon(":/resources/countries/et.svg"), "et"); + countryEdit->addItem(QIcon(":/resources/countries/fi.svg"), "fi"); + countryEdit->addItem(QIcon(":/resources/countries/fj.svg"), "fj"); + countryEdit->addItem(QIcon(":/resources/countries/fk.svg"), "fk"); + countryEdit->addItem(QIcon(":/resources/countries/fm.svg"), "fm"); + countryEdit->addItem(QIcon(":/resources/countries/fo.svg"), "fo"); + countryEdit->addItem(QIcon(":/resources/countries/fr.svg"), "fr"); + countryEdit->addItem(QIcon(":/resources/countries/ga.svg"), "ga"); + countryEdit->addItem(QIcon(":/resources/countries/gb.svg"), "gb"); + countryEdit->addItem(QIcon(":/resources/countries/gd.svg"), "gd"); + countryEdit->addItem(QIcon(":/resources/countries/ge.svg"), "ge"); + countryEdit->addItem(QIcon(":/resources/countries/gf.svg"), "gf"); + countryEdit->addItem(QIcon(":/resources/countries/gg.svg"), "gg"); + countryEdit->addItem(QIcon(":/resources/countries/gh.svg"), "gh"); + countryEdit->addItem(QIcon(":/resources/countries/gi.svg"), "gi"); + countryEdit->addItem(QIcon(":/resources/countries/gl.svg"), "gl"); + countryEdit->addItem(QIcon(":/resources/countries/gm.svg"), "gm"); + countryEdit->addItem(QIcon(":/resources/countries/gn.svg"), "gn"); + countryEdit->addItem(QIcon(":/resources/countries/gp.svg"), "gp"); + countryEdit->addItem(QIcon(":/resources/countries/gq.svg"), "gq"); + countryEdit->addItem(QIcon(":/resources/countries/gr.svg"), "gr"); + countryEdit->addItem(QIcon(":/resources/countries/gs.svg"), "gs"); + countryEdit->addItem(QIcon(":/resources/countries/gt.svg"), "gt"); + countryEdit->addItem(QIcon(":/resources/countries/gu.svg"), "gu"); + countryEdit->addItem(QIcon(":/resources/countries/gw.svg"), "gw"); + countryEdit->addItem(QIcon(":/resources/countries/gy.svg"), "gy"); + countryEdit->addItem(QIcon(":/resources/countries/hk.svg"), "hk"); + countryEdit->addItem(QIcon(":/resources/countries/hm.svg"), "hm"); + countryEdit->addItem(QIcon(":/resources/countries/hn.svg"), "hn"); + countryEdit->addItem(QIcon(":/resources/countries/hr.svg"), "hr"); + countryEdit->addItem(QIcon(":/resources/countries/ht.svg"), "ht"); + countryEdit->addItem(QIcon(":/resources/countries/hu.svg"), "hu"); + countryEdit->addItem(QIcon(":/resources/countries/id.svg"), "id"); + countryEdit->addItem(QIcon(":/resources/countries/ie.svg"), "ie"); + countryEdit->addItem(QIcon(":/resources/countries/il.svg"), "il"); + countryEdit->addItem(QIcon(":/resources/countries/im.svg"), "im"); + countryEdit->addItem(QIcon(":/resources/countries/in.svg"), "in"); + countryEdit->addItem(QIcon(":/resources/countries/io.svg"), "io"); + countryEdit->addItem(QIcon(":/resources/countries/iq.svg"), "iq"); + countryEdit->addItem(QIcon(":/resources/countries/ir.svg"), "ir"); + countryEdit->addItem(QIcon(":/resources/countries/is.svg"), "is"); + countryEdit->addItem(QIcon(":/resources/countries/it.svg"), "it"); + countryEdit->addItem(QIcon(":/resources/countries/je.svg"), "je"); + countryEdit->addItem(QIcon(":/resources/countries/jm.svg"), "jm"); + countryEdit->addItem(QIcon(":/resources/countries/jo.svg"), "jo"); + countryEdit->addItem(QIcon(":/resources/countries/jp.svg"), "jp"); + countryEdit->addItem(QIcon(":/resources/countries/ke.svg"), "ke"); + countryEdit->addItem(QIcon(":/resources/countries/kg.svg"), "kg"); + countryEdit->addItem(QIcon(":/resources/countries/kh.svg"), "kh"); + countryEdit->addItem(QIcon(":/resources/countries/ki.svg"), "ki"); + countryEdit->addItem(QIcon(":/resources/countries/km.svg"), "km"); + countryEdit->addItem(QIcon(":/resources/countries/kn.svg"), "kn"); + countryEdit->addItem(QIcon(":/resources/countries/kp.svg"), "kp"); + countryEdit->addItem(QIcon(":/resources/countries/kr.svg"), "kr"); + countryEdit->addItem(QIcon(":/resources/countries/kw.svg"), "kw"); + countryEdit->addItem(QIcon(":/resources/countries/ky.svg"), "ky"); + countryEdit->addItem(QIcon(":/resources/countries/kz.svg"), "kz"); + countryEdit->addItem(QIcon(":/resources/countries/la.svg"), "la"); + countryEdit->addItem(QIcon(":/resources/countries/lb.svg"), "lb"); + countryEdit->addItem(QIcon(":/resources/countries/lc.svg"), "lc"); + countryEdit->addItem(QIcon(":/resources/countries/li.svg"), "li"); + countryEdit->addItem(QIcon(":/resources/countries/lk.svg"), "lk"); + countryEdit->addItem(QIcon(":/resources/countries/lr.svg"), "lr"); + countryEdit->addItem(QIcon(":/resources/countries/ls.svg"), "ls"); + countryEdit->addItem(QIcon(":/resources/countries/lt.svg"), "lt"); + countryEdit->addItem(QIcon(":/resources/countries/lu.svg"), "lu"); + countryEdit->addItem(QIcon(":/resources/countries/lv.svg"), "lv"); + countryEdit->addItem(QIcon(":/resources/countries/ly.svg"), "ly"); + countryEdit->addItem(QIcon(":/resources/countries/ma.svg"), "ma"); + countryEdit->addItem(QIcon(":/resources/countries/mc.svg"), "mc"); + countryEdit->addItem(QIcon(":/resources/countries/md.svg"), "md"); + countryEdit->addItem(QIcon(":/resources/countries/me.svg"), "me"); + countryEdit->addItem(QIcon(":/resources/countries/mf.svg"), "mf"); + countryEdit->addItem(QIcon(":/resources/countries/mg.svg"), "mg"); + countryEdit->addItem(QIcon(":/resources/countries/mh.svg"), "mh"); + countryEdit->addItem(QIcon(":/resources/countries/mk.svg"), "mk"); + countryEdit->addItem(QIcon(":/resources/countries/ml.svg"), "ml"); + countryEdit->addItem(QIcon(":/resources/countries/mm.svg"), "mm"); + countryEdit->addItem(QIcon(":/resources/countries/mn.svg"), "mn"); + countryEdit->addItem(QIcon(":/resources/countries/mo.svg"), "mo"); + countryEdit->addItem(QIcon(":/resources/countries/mp.svg"), "mp"); + countryEdit->addItem(QIcon(":/resources/countries/mq.svg"), "mq"); + countryEdit->addItem(QIcon(":/resources/countries/mr.svg"), "mr"); + countryEdit->addItem(QIcon(":/resources/countries/ms.svg"), "ms"); + countryEdit->addItem(QIcon(":/resources/countries/mt.svg"), "mt"); + countryEdit->addItem(QIcon(":/resources/countries/mu.svg"), "mu"); + countryEdit->addItem(QIcon(":/resources/countries/mv.svg"), "mv"); + countryEdit->addItem(QIcon(":/resources/countries/mw.svg"), "mw"); + countryEdit->addItem(QIcon(":/resources/countries/mx.svg"), "mx"); + countryEdit->addItem(QIcon(":/resources/countries/my.svg"), "my"); + countryEdit->addItem(QIcon(":/resources/countries/mz.svg"), "mz"); + countryEdit->addItem(QIcon(":/resources/countries/na.svg"), "na"); + countryEdit->addItem(QIcon(":/resources/countries/nc.svg"), "nc"); + countryEdit->addItem(QIcon(":/resources/countries/ne.svg"), "ne"); + countryEdit->addItem(QIcon(":/resources/countries/nf.svg"), "nf"); + countryEdit->addItem(QIcon(":/resources/countries/ng.svg"), "ng"); + countryEdit->addItem(QIcon(":/resources/countries/ni.svg"), "ni"); + countryEdit->addItem(QIcon(":/resources/countries/nl.svg"), "nl"); + countryEdit->addItem(QIcon(":/resources/countries/no.svg"), "no"); + countryEdit->addItem(QIcon(":/resources/countries/np.svg"), "np"); + countryEdit->addItem(QIcon(":/resources/countries/nr.svg"), "nr"); + countryEdit->addItem(QIcon(":/resources/countries/nu.svg"), "nu"); + countryEdit->addItem(QIcon(":/resources/countries/nz.svg"), "nz"); + countryEdit->addItem(QIcon(":/resources/countries/om.svg"), "om"); + countryEdit->addItem(QIcon(":/resources/countries/pa.svg"), "pa"); + countryEdit->addItem(QIcon(":/resources/countries/pe.svg"), "pe"); + countryEdit->addItem(QIcon(":/resources/countries/pf.svg"), "pf"); + countryEdit->addItem(QIcon(":/resources/countries/pg.svg"), "pg"); + countryEdit->addItem(QIcon(":/resources/countries/ph.svg"), "ph"); + countryEdit->addItem(QIcon(":/resources/countries/pk.svg"), "pk"); + countryEdit->addItem(QIcon(":/resources/countries/pl.svg"), "pl"); + countryEdit->addItem(QIcon(":/resources/countries/pm.svg"), "pm"); + countryEdit->addItem(QIcon(":/resources/countries/pn.svg"), "pn"); + countryEdit->addItem(QIcon(":/resources/countries/pr.svg"), "pr"); + countryEdit->addItem(QIcon(":/resources/countries/ps.svg"), "ps"); + countryEdit->addItem(QIcon(":/resources/countries/pt.svg"), "pt"); + countryEdit->addItem(QIcon(":/resources/countries/pw.svg"), "pw"); + countryEdit->addItem(QIcon(":/resources/countries/py.svg"), "py"); + countryEdit->addItem(QIcon(":/resources/countries/qa.svg"), "qa"); + countryEdit->addItem(QIcon(":/resources/countries/re.svg"), "re"); + countryEdit->addItem(QIcon(":/resources/countries/ro.svg"), "ro"); + countryEdit->addItem(QIcon(":/resources/countries/rs.svg"), "rs"); + countryEdit->addItem(QIcon(":/resources/countries/ru.svg"), "ru"); + countryEdit->addItem(QIcon(":/resources/countries/rw.svg"), "rw"); + countryEdit->addItem(QIcon(":/resources/countries/sa.svg"), "sa"); + countryEdit->addItem(QIcon(":/resources/countries/sb.svg"), "sb"); + countryEdit->addItem(QIcon(":/resources/countries/sc.svg"), "sc"); + countryEdit->addItem(QIcon(":/resources/countries/sd.svg"), "sd"); + countryEdit->addItem(QIcon(":/resources/countries/se.svg"), "se"); + countryEdit->addItem(QIcon(":/resources/countries/sg.svg"), "sg"); + countryEdit->addItem(QIcon(":/resources/countries/sh.svg"), "sh"); + countryEdit->addItem(QIcon(":/resources/countries/si.svg"), "si"); + countryEdit->addItem(QIcon(":/resources/countries/sj.svg"), "sj"); + countryEdit->addItem(QIcon(":/resources/countries/sk.svg"), "sk"); + countryEdit->addItem(QIcon(":/resources/countries/sl.svg"), "sl"); + countryEdit->addItem(QIcon(":/resources/countries/sm.svg"), "sm"); + countryEdit->addItem(QIcon(":/resources/countries/sn.svg"), "sn"); + countryEdit->addItem(QIcon(":/resources/countries/so.svg"), "so"); + countryEdit->addItem(QIcon(":/resources/countries/sr.svg"), "sr"); + countryEdit->addItem(QIcon(":/resources/countries/ss.svg"), "ss"); + countryEdit->addItem(QIcon(":/resources/countries/st.svg"), "st"); + countryEdit->addItem(QIcon(":/resources/countries/sv.svg"), "sv"); + countryEdit->addItem(QIcon(":/resources/countries/sx.svg"), "sx"); + countryEdit->addItem(QIcon(":/resources/countries/sy.svg"), "sy"); + countryEdit->addItem(QIcon(":/resources/countries/sz.svg"), "sz"); + countryEdit->addItem(QIcon(":/resources/countries/tc.svg"), "tc"); + countryEdit->addItem(QIcon(":/resources/countries/td.svg"), "td"); + countryEdit->addItem(QIcon(":/resources/countries/tf.svg"), "tf"); + countryEdit->addItem(QIcon(":/resources/countries/tg.svg"), "tg"); + countryEdit->addItem(QIcon(":/resources/countries/th.svg"), "th"); + countryEdit->addItem(QIcon(":/resources/countries/tj.svg"), "tj"); + countryEdit->addItem(QIcon(":/resources/countries/tk.svg"), "tk"); + countryEdit->addItem(QIcon(":/resources/countries/tl.svg"), "tl"); + countryEdit->addItem(QIcon(":/resources/countries/tm.svg"), "tm"); + countryEdit->addItem(QIcon(":/resources/countries/tn.svg"), "tn"); + countryEdit->addItem(QIcon(":/resources/countries/to.svg"), "to"); + countryEdit->addItem(QIcon(":/resources/countries/tr.svg"), "tr"); + countryEdit->addItem(QIcon(":/resources/countries/tt.svg"), "tt"); + countryEdit->addItem(QIcon(":/resources/countries/tv.svg"), "tv"); + countryEdit->addItem(QIcon(":/resources/countries/tw.svg"), "tw"); + countryEdit->addItem(QIcon(":/resources/countries/tz.svg"), "tz"); + countryEdit->addItem(QIcon(":/resources/countries/ua.svg"), "ua"); + countryEdit->addItem(QIcon(":/resources/countries/ug.svg"), "ug"); + countryEdit->addItem(QIcon(":/resources/countries/um.svg"), "um"); + countryEdit->addItem(QIcon(":/resources/countries/us.svg"), "us"); + countryEdit->addItem(QIcon(":/resources/countries/uy.svg"), "uy"); + countryEdit->addItem(QIcon(":/resources/countries/uz.svg"), "uz"); + countryEdit->addItem(QIcon(":/resources/countries/va.svg"), "va"); + countryEdit->addItem(QIcon(":/resources/countries/vc.svg"), "vc"); + countryEdit->addItem(QIcon(":/resources/countries/ve.svg"), "ve"); + countryEdit->addItem(QIcon(":/resources/countries/vg.svg"), "vg"); + countryEdit->addItem(QIcon(":/resources/countries/vi.svg"), "vi"); + countryEdit->addItem(QIcon(":/resources/countries/vn.svg"), "vn"); + countryEdit->addItem(QIcon(":/resources/countries/vu.svg"), "vu"); + countryEdit->addItem(QIcon(":/resources/countries/wf.svg"), "wf"); + countryEdit->addItem(QIcon(":/resources/countries/ws.svg"), "ws"); + countryEdit->addItem(QIcon(":/resources/countries/ye.svg"), "ye"); + countryEdit->addItem(QIcon(":/resources/countries/yt.svg"), "yt"); + countryEdit->addItem(QIcon(":/resources/countries/za.svg"), "za"); + countryEdit->addItem(QIcon(":/resources/countries/zm.svg"), "zm"); + countryEdit->addItem(QIcon(":/resources/countries/zw.svg"), "zw"); + countryEdit->setCurrentIndex(0); + + realnameLabel = new QLabel(tr("Real name:")); + realnameEdit = new QLineEdit(); + realnameLabel->setBuddy(realnameEdit); + + QGridLayout *grid = new QGridLayout; + grid->addWidget(hostLabel, 0, 0); + grid->addWidget(hostEdit, 0, 1); + grid->addWidget(portLabel, 1, 0); + grid->addWidget(portEdit, 1, 1); + grid->addWidget(playernameLabel, 2, 0); + grid->addWidget(playernameEdit, 2, 1); + grid->addWidget(passwordLabel, 3, 0); + grid->addWidget(passwordEdit, 3, 1); + grid->addWidget(emailLabel, 4, 0); + grid->addWidget(emailEdit, 4, 1); + grid->addWidget(genderLabel, 5, 0); + grid->addWidget(genderEdit, 5, 1); + grid->addWidget(countryLabel, 6, 0); + grid->addWidget(countryEdit, 6, 1); + grid->addWidget(realnameLabel, 7, 0); + grid->addWidget(realnameEdit, 7, 1); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(buttonBox, SIGNAL(accepted()), this, SLOT(actOk())); + connect(buttonBox, SIGNAL(rejected()), this, SLOT(actCancel())); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(grid); + mainLayout->addWidget(buttonBox); + setLayout(mainLayout); + + setWindowTitle(tr("Register to server")); + setFixedHeight(sizeHint().height()); + setMinimumWidth(300); +} + +void DlgRegister::actOk() +{ + QSettings settings; + settings.beginGroup("server"); + settings.setValue("hostname", hostEdit->text()); + settings.setValue("port", portEdit->text()); + settings.setValue("playername", playernameEdit->text()); + // always save the password so it will be picked up by the connect dialog + settings.setValue("password", passwordEdit->text()); + settings.endGroup(); + + accept(); +} + +void DlgRegister::actCancel() +{ + reject(); +} diff --git a/cockatrice/src/dlg_register.h b/cockatrice/src/dlg_register.h new file mode 100644 index 00000000..3ee7b105 --- /dev/null +++ b/cockatrice/src/dlg_register.h @@ -0,0 +1,33 @@ +#ifndef DLG_REGISTER_H +#define DLG_REGISTER_H + +#include +#include +#include + +class QLabel; +class QPushButton; +class QCheckBox; + +class DlgRegister : public QDialog { + Q_OBJECT +public: + DlgRegister(QWidget *parent = 0); + QString getHost() const { return hostEdit->text(); } + int getPort() const { return portEdit->text().toInt(); } + QString getPlayerName() const { return playernameEdit->text(); } + QString getPassword() const { return passwordEdit->text(); } + QString getEmail() const { return emailEdit->text(); } + int getGender() const { return genderEdit->currentIndex() - 1; } + QString getCountry() const { return genderEdit->currentIndex() == 0 ? "" : countryEdit->currentText(); } + QString getRealName() const { return realnameEdit->text(); } +private slots: + void actOk(); + void actCancel(); +private: + QLabel *hostLabel, *portLabel, *playernameLabel, *passwordLabel, *emailLabel, *genderLabel, *countryLabel, *realnameLabel; + QLineEdit *hostEdit, *portEdit, *playernameEdit, *passwordEdit, *emailEdit, *realnameEdit; + QComboBox *genderEdit, *countryEdit; +}; + +#endif diff --git a/cockatrice/src/remoteclient.cpp b/cockatrice/src/remoteclient.cpp index 4f8deaed..3a597589 100644 --- a/cockatrice/src/remoteclient.cpp +++ b/cockatrice/src/remoteclient.cpp @@ -6,6 +6,8 @@ #include "pb/commands.pb.h" #include "pb/session_commands.pb.h" #include "pb/response_login.pb.h" +#include "pb/response_register.pb.h" +#include "pb/response_activate.pb.h" #include "pb/server_message.pb.h" #include "pb/event_server_identification.pb.h" @@ -28,6 +30,8 @@ RemoteClient::RemoteClient(QObject *parent) connect(this, SIGNAL(connectionClosedEventReceived(Event_ConnectionClosed)), this, SLOT(processConnectionClosedEvent(Event_ConnectionClosed))); connect(this, SIGNAL(sigConnectToServer(QString, unsigned int, QString, QString)), this, SLOT(doConnectToServer(QString, unsigned int, QString, QString))); connect(this, SIGNAL(sigDisconnectFromServer()), this, SLOT(doDisconnectFromServer())); + connect(this, SIGNAL(sigRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString)), this, SLOT(doRegisterToServer(QString, unsigned int, QString, QString, QString, int, QString, QString))); + connect(this, SIGNAL(sigActivateToServer(QString)), this, SLOT(doActivateToServer(QString))); } RemoteClient::~RemoteClient() @@ -52,8 +56,6 @@ void RemoteClient::slotConnected() sendCommandContainer(CommandContainer()); getNewCmdId(); // end of hack - - setStatus(StatusAwaitingWelcome); } void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentification &event) @@ -63,6 +65,42 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica setStatus(StatusDisconnecting); return; } + + if(getStatus() == StatusRegistering) + { + Command_Register cmdRegister; + cmdRegister.set_user_name(userName.toStdString()); + cmdRegister.set_password(password.toStdString()); + cmdRegister.set_email(email.toStdString()); + cmdRegister.set_gender((ServerInfo_User_Gender) gender); + cmdRegister.set_country(country.toStdString()); + cmdRegister.set_real_name(realName.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdRegister); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(registerResponse(Response))); + sendCommand(pend); + + return; + } + + if(getStatus() == StatusActivating) + { + Command_Activate cmdActivate; + cmdActivate.set_user_name(userName.toStdString()); + cmdActivate.set_token(token.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdActivate); + connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(activateResponse(Response))); + sendCommand(pend); + + return; + } + + doLogin(); +} + +void RemoteClient::doLogin() +{ setStatus(StatusLoggingIn); Command_Login cmdLogin; @@ -101,6 +139,38 @@ void RemoteClient::loginResponse(const Response &response) } } +void RemoteClient::registerResponse(const Response &response) +{ + const Response_Register &resp = response.GetExtension(Response_Register::ext); + switch(response.response_code()) + { + case Response::RespRegistrationAccepted: + emit registerAccepted(); + doLogin(); + break; + case Response::RespRegistrationAcceptedNeedsActivation: + emit registerAcceptedNeedsActivate(); + doLogin(); + break; + default: + emit registerError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), resp.denied_end_time()); + setStatus(StatusDisconnecting); + doDisconnectFromServer(); + break; + } +} + +void RemoteClient::activateResponse(const Response &response) +{ + if (response.response_code() == Response::RespActivationAccepted) { + emit activateAccepted(); + + doLogin(); + } else { + emit activateError(); + } +} + void RemoteClient::readData() { lastDataReceived = timeRunning; @@ -171,10 +241,40 @@ void RemoteClient::doConnectToServer(const QString &hostname, unsigned int port, userName = _userName; password = _password; + lastHostname = hostname; + lastPort = port; + socket->connectToHost(hostname, port); setStatus(StatusConnecting); } +void RemoteClient::doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname) +{ + doDisconnectFromServer(); + + userName = _userName; + password = _password; + email = _email; + gender = _gender; + country = _country; + realName = _realname; + lastHostname = hostname; + lastPort = port; + + socket->connectToHost(hostname, port); + setStatus(StatusRegistering); +} + +void RemoteClient::doActivateToServer(const QString &_token) +{ + doDisconnectFromServer(); + + token = _token; + + socket->connectToHost(lastHostname, lastPort); + setStatus(StatusActivating); +} + void RemoteClient::doDisconnectFromServer() { timer->stop(); @@ -225,6 +325,16 @@ void RemoteClient::connectToServer(const QString &hostname, unsigned int port, c emit sigConnectToServer(hostname, port, _userName, _password); } +void RemoteClient::registerToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname) +{ + emit sigRegisterToServer(hostname, port, _userName, _password, _email, _gender, _country, _realname); +} + +void RemoteClient::activateToServer(const QString &_token) +{ + emit sigActivateToServer(_token); +} + void RemoteClient::disconnectFromServer() { emit sigDisconnectFromServer(); diff --git a/cockatrice/src/remoteclient.h b/cockatrice/src/remoteclient.h index 2cb84e90..c1478732 100644 --- a/cockatrice/src/remoteclient.h +++ b/cockatrice/src/remoteclient.h @@ -12,10 +12,14 @@ signals: void maxPingTime(int seconds, int maxSeconds); void serverTimeout(); void loginError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void registerError(Response::ResponseCode resp, QString reasonStr, quint32 endTime); + void activateError(); void socketError(const QString &errorString); void protocolVersionMismatch(int clientVersion, int serverVersion); void protocolError(); void sigConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void sigRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void sigActivateToServer(const QString &_token); void sigDisconnectFromServer(); private slots: void slotConnected(); @@ -25,8 +29,14 @@ private slots: void processServerIdentificationEvent(const Event_ServerIdentification &event); void processConnectionClosedEvent(const Event_ConnectionClosed &event); void loginResponse(const Response &response); + void registerResponse(const Response &response); + void activateResponse(const Response &response); void doConnectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void doRegisterToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void doLogin(); void doDisconnectFromServer(); + void doActivateToServer(const QString &_token); + private: static const int maxTimeout = 10; int timeRunning, lastDataReceived; @@ -38,6 +48,8 @@ private: QTimer *timer; QTcpSocket *socket; + QString lastHostname; + int lastPort; protected slots: void sendCommandContainer(const CommandContainer &cont); public: @@ -45,6 +57,8 @@ public: ~RemoteClient(); QString peerName() const { return socket->peerName(); } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + void registerToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password, const QString &_email, const int _gender, const QString &_country, const QString &_realname); + void activateToServer(const QString &_token); void disconnectFromServer(); }; diff --git a/cockatrice/src/window_main.cpp b/cockatrice/src/window_main.cpp index 46ac9376..3a0d4044 100644 --- a/cockatrice/src/window_main.cpp +++ b/cockatrice/src/window_main.cpp @@ -34,6 +34,7 @@ #include "main.h" #include "window_main.h" #include "dlg_connect.h" +#include "dlg_register.h" #include "dlg_settings.h" #include "tab_supervisor.h" #include "remoteclient.h" @@ -97,21 +98,22 @@ void MainWindow::statusChanged(ClientStatus _status) { setClientStatusTitle(); switch (_status) { - case StatusConnecting: - break; case StatusDisconnected: tabSupervisor->stop(); aSinglePlayer->setEnabled(true); aConnect->setEnabled(true); + aRegister->setEnabled(true); aDisconnect->setEnabled(false); break; case StatusLoggingIn: aSinglePlayer->setEnabled(false); aConnect->setEnabled(false); + aRegister->setEnabled(false); aDisconnect->setEnabled(true); break; + case StatusConnecting: + case StatusRegistering: case StatusLoggedIn: - break; default: break; } @@ -122,6 +124,21 @@ void MainWindow::userInfoReceived(const ServerInfo_User &info) tabSupervisor->start(info); } +void MainWindow::registerAccepted() +{ + QMessageBox::information(this, tr("Success"), tr("Registration accepted.\nWill now login.")); +} + +void MainWindow::registerAcceptedNeedsActivate() +{ + // nothing +} + +void MainWindow::activateAccepted() +{ + QMessageBox::information(this, tr("Success"), tr("Account activation accepted.\nWill now login.")); +} + // Actions void MainWindow::actConnect() @@ -131,6 +148,24 @@ void MainWindow::actConnect() client->connectToServer(dlg.getHost(), dlg.getPort(), dlg.getPlayerName(), dlg.getPassword()); } +void MainWindow::actRegister() +{ + DlgRegister dlg(this); + if (dlg.exec()) + { + client->registerToServer( + dlg.getHost(), + dlg.getPort(), + dlg.getPlayerName(), + dlg.getPassword(), + dlg.getEmail(), + dlg.getGender(), + dlg.getCountry(), + dlg.getRealName() + ); + } +} + void MainWindow::actDisconnect() { client->disconnectFromServer(); @@ -144,6 +179,7 @@ void MainWindow::actSinglePlayer() return; aConnect->setEnabled(false); + aRegister->setEnabled(false); aSinglePlayer->setEnabled(false); localServer = new LocalServer(this); @@ -191,6 +227,7 @@ void MainWindow::localGameEnded() localServer = 0; aConnect->setEnabled(true); + aRegister->setEnabled(true); aSinglePlayer->setEnabled(true); } @@ -269,11 +306,74 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32 QMessageBox::critical(this, tr("Error"), tr("Invalid username.\nYou may only use A-Z, a-z, 0-9, _, ., and - in your username.")); break; case Response::RespRegistrationRequired: - QMessageBox::critical(this, tr("Error"), tr("This server requires user registration.")); + if (QMessageBox::question(this, tr("Error"), tr("This server requires user registration. Do you want to register now?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + actRegister(); + } + break; + case Response::RespAccountNotActivated: { + bool ok = false; + QString token = QInputDialog::getText(this, tr("Account activation"), tr("Your account has not been activated yet.\n You need to provide the activation token received in the activation email"), QLineEdit::Normal, QString(), &ok); + if(ok && !token.isEmpty()) + { + client->activateToServer(token); + return; + } + client->disconnectFromServer(); + break; + } + default: + QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); + break; + } + actConnect(); +} + +void MainWindow::registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime) +{ + switch (r) { + case Response::RespRegistrationDisabled: + QMessageBox::critical(this, tr("Registration denied"), tr("Registration is currently disabled on this server")); + break; + case Response::RespUserAlreadyExists: + QMessageBox::critical(this, tr("Registration denied"), tr("There is already an existing account with the same user name.")); + break; + case Response::RespEmailRequiredToRegister: + QMessageBox::critical(this, tr("Registration denied"), tr("It's mandatory to specify a valid email address when registering.")); + break; + case Response::RespTooManyRequests: + QMessageBox::critical(this, tr("Registration denied"), tr("Too many registration attempts from your IP address.")); + break; + case Response::RespPasswordTooShort: + QMessageBox::critical(this, tr("Registration denied"), tr("Password too short.")); + break; + case Response::RespUserIsBanned: { + QString bannedStr; + if (endTime) + bannedStr = tr("You are banned until %1.").arg(QDateTime::fromTime_t(endTime).toString()); + else + bannedStr = tr("You are banned indefinitely."); + if (!reasonStr.isEmpty()) + bannedStr.append("\n\n" + reasonStr); + + QMessageBox::critical(this, tr("Error"), bannedStr); + break; + } + case Response::RespUsernameInvalid: + QMessageBox::critical(this, tr("Error"), tr("Invalid username.\nYou may only use A-Z, a-z, 0-9, _, ., and - in your username.")); + break; + case Response::RespRegistrationFailed: + QMessageBox::critical(this, tr("Error"), tr("Registration failed for a technical problem on the server.")); break; default: QMessageBox::critical(this, tr("Error"), tr("Unknown login error: %1").arg(static_cast(r))); } + actRegister(); +} + +void MainWindow::activateError() +{ + QMessageBox::critical(this, tr("Error"), tr("Account activation failed")); + client->disconnectFromServer(); actConnect(); } @@ -295,6 +395,7 @@ void MainWindow::setClientStatusTitle() { switch (client->getStatus()) { case StatusConnecting: setWindowTitle(appName + " - " + tr("Connecting to %1...").arg(client->peerName())); break; + case StatusRegistering: setWindowTitle(appName + " - " + tr("Registering to %1 as %2...").arg(client->peerName()).arg(client->getUserName())); break; case StatusDisconnected: setWindowTitle(appName + " - " + tr("Disconnected")); break; case StatusLoggingIn: setWindowTitle(appName + " - " + tr("Connected, logging in at %1").arg(client->peerName())); break; case StatusLoggedIn: setWindowTitle(appName + " - " + tr("Logged in as %1 at %2").arg(client->getUserName()).arg(client->peerName())); break; @@ -313,6 +414,7 @@ void MainWindow::retranslateUi() aDeckEditor->setText(tr("&Deck editor")); aFullScreen->setText(tr("&Full screen")); aFullScreen->setShortcut(QKeySequence("Ctrl+F")); + aRegister->setText(tr("&Register to server...")); aSettings->setText(tr("&Settings...")); aExit->setText(tr("&Exit")); @@ -344,6 +446,8 @@ void MainWindow::createActions() aFullScreen = new QAction(this); aFullScreen->setCheckable(true); connect(aFullScreen, SIGNAL(toggled(bool)), this, SLOT(actFullScreen(bool))); + aRegister = new QAction(this); + connect(aRegister, SIGNAL(triggered()), this, SLOT(actRegister())); aSettings = new QAction(this); connect(aSettings, SIGNAL(triggered()), this, SLOT(actSettings())); aExit = new QAction(this); @@ -376,6 +480,7 @@ void MainWindow::createMenus() cockatriceMenu = menuBar()->addMenu(QString()); cockatriceMenu->addAction(aConnect); cockatriceMenu->addAction(aDisconnect); + cockatriceMenu->addAction(aRegister); cockatriceMenu->addSeparator(); cockatriceMenu->addAction(aSinglePlayer); cockatriceMenu->addAction(aWatchReplay); @@ -408,7 +513,13 @@ MainWindow::MainWindow(QWidget *parent) connect(client, SIGNAL(statusChanged(ClientStatus)), this, SLOT(statusChanged(ClientStatus))); connect(client, SIGNAL(protocolVersionMismatch(int, int)), this, SLOT(protocolVersionMismatch(int, int))); connect(client, SIGNAL(userInfoChanged(const ServerInfo_User &)), this, SLOT(userInfoReceived(const ServerInfo_User &)), Qt::BlockingQueuedConnection); - + + connect(client, SIGNAL(registerAccepted()), this, SLOT(registerAccepted())); + connect(client, SIGNAL(registerAcceptedNeedsActivate()), this, SLOT(registerAcceptedNeedsActivate())); + connect(client, SIGNAL(registerError(Response::ResponseCode, QString, quint32)), this, SLOT(registerError(Response::ResponseCode, QString, quint32))); + connect(client, SIGNAL(activateAccepted()), this, SLOT(activateAccepted())); + connect(client, SIGNAL(activateError()), this, SLOT(activateError())); + clientThread = new QThread(this); client->moveToThread(clientThread); clientThread->start(); diff --git a/cockatrice/src/window_main.h b/cockatrice/src/window_main.h index aed256dd..57b9e780 100644 --- a/cockatrice/src/window_main.h +++ b/cockatrice/src/window_main.h @@ -43,9 +43,14 @@ private slots: void processServerShutdownEvent(const Event_ServerShutdown &event); void serverTimeout(); void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime); + void activateError(); void socketError(const QString &errorStr); void protocolVersionMismatch(int localVersion, int remoteVersion); void userInfoReceived(const ServerInfo_User &userInfo); + void registerAccepted(); + void registerAcceptedNeedsActivate(); + void activateAccepted(); void localGameEnded(); void pixmapCacheSizeChanged(int newSizeInMBs); @@ -55,6 +60,7 @@ private slots: void actWatchReplay(); void actDeckEditor(); void actFullScreen(bool checked); + void actRegister(); void actSettings(); void actExit(); @@ -82,7 +88,7 @@ private: QList tabMenus; QMenu *cockatriceMenu, *helpMenu; QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit, - *aAbout, *aCheckCardUpdates; + *aAbout, *aCheckCardUpdates, *aRegister; TabSupervisor *tabSupervisor; QMenu *trayIconMenu; diff --git a/common/pb/CMakeLists.txt b/common/pb/CMakeLists.txt index adf1e1b8..db5cd130 100644 --- a/common/pb/CMakeLists.txt +++ b/common/pb/CMakeLists.txt @@ -114,6 +114,7 @@ SET(PROTO_FILES isl_message.proto moderator_commands.proto move_card_to_zone.proto + response_activate.proto response_deck_download.proto response_deck_list.proto response_deck_upload.proto @@ -123,6 +124,7 @@ SET(PROTO_FILES response_join_room.proto response_list_users.proto response_login.proto + response_register.proto response_replay_download.proto response_replay_list.proto response.proto diff --git a/common/pb/response.proto b/common/pb/response.proto index 0f13c415..4955bed5 100644 --- a/common/pb/response.proto +++ b/common/pb/response.proto @@ -24,6 +24,17 @@ message Response { RespAccessDenied = 20; RespUsernameInvalid = 21; RespRegistrationRequired = 22; + RespRegistrationAccepted = 23; // Server agrees to process client's registration request + RespUserAlreadyExists = 24; // Client attempted to register a name which is already registered + RespEmailRequiredToRegister = 25; // Server requires email to register accounts but client did not provide one + RespTooManyRequests = 26; // Server refused to complete command because client has sent too many too quickly + RespPasswordTooShort = 27; // Server requires a decent password + RespAccountNotActivated = 28; // Client attempted to log into a registered username but the account hasn't been activated + RespRegistrationDisabled = 29; // Server does not allow clients to register + RespRegistrationFailed = 30; // Server accepted a reg request but failed to perform the registration + RespActivationAccepted = 31; // Server accepted a reg user activation token + RespActivationFailed = 32; // Server didn't accept a reg user activation token + RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation } enum ResponseType { JOIN_ROOM = 1000; @@ -35,6 +46,8 @@ message Response { DECK_LIST = 1006; DECK_DOWNLOAD = 1007; DECK_UPLOAD = 1008; + REGISTER = 1009; + ACTIVATE = 1010; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; } diff --git a/common/pb/response_activate.proto b/common/pb/response_activate.proto new file mode 100644 index 00000000..25667534 --- /dev/null +++ b/common/pb/response_activate.proto @@ -0,0 +1,7 @@ +import "response.proto"; + +message Response_Activate { + extend Response { + optional Response_Activate ext = 1010; + } +} \ No newline at end of file diff --git a/common/pb/response_register.proto b/common/pb/response_register.proto new file mode 100644 index 00000000..9c6998ef --- /dev/null +++ b/common/pb/response_register.proto @@ -0,0 +1,9 @@ +import "response.proto"; + +message Response_Register { + extend Response { + optional Response_Register ext = 1009; + } + optional string denied_reason_str = 1; + optional uint64 denied_end_time = 2; +} \ No newline at end of file diff --git a/common/pb/session_commands.proto b/common/pb/session_commands.proto index bbb5e81d..11e5ce6f 100644 --- a/common/pb/session_commands.proto +++ b/common/pb/session_commands.proto @@ -1,3 +1,5 @@ +import "serverinfo_user.proto"; + message SessionCommand { enum SessionCommandType { PING = 1000; @@ -16,6 +18,8 @@ message SessionCommand { DECK_UPLOAD = 1013; LIST_ROOMS = 1014; JOIN_ROOM = 1015; + REGISTER = 1016; + ACTIVATE = 1017; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; REPLAY_MODIFY_MATCH = 1102; @@ -94,3 +98,32 @@ message Command_JoinRoom { } optional uint32 room_id = 1; } + +// User wants to register a new account +message Command_Register { + extend SessionCommand { + optional Command_Register ext = 1016; + } + // User name client wants to register + required string user_name = 1; + // Hashed password to be inserted into database + required string password = 2; + // Email address of the client for user validation + optional string email = 3; + // Gender of the user + optional ServerInfo_User.Gender gender = 4; + // Country code of the user. 2 letter ISO format + optional string country = 5; + optional string real_name = 6; +} + +// User wants to activate an account +message Command_Activate { + extend SessionCommand { + optional Command_Activate ext = 1017; + } + // User name client wants to activate + required string user_name = 1; + // Activation token + required string token = 2; +} diff --git a/common/server.cpp b/common/server.cpp index 4b65739b..3c2c9718 100644 --- a/common/server.cpp +++ b/common/server.cpp @@ -113,7 +113,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QWriteLocker locker(&clientsLock); AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, reasonStr, secondsLeft); - if ((authState == NotLoggedIn) || (authState == UserIsBanned || authState == UsernameInvalid)) + if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid || authState == UserIsInactive) return authState; ServerInfo_User data = databaseInterface->getUserData(name, true); @@ -140,7 +140,7 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString QString tempName = name; int i = 0; - while (users.contains(tempName) || databaseInterface->userExists(tempName) || databaseInterface->userSessionExists(tempName)) + while (users.contains(tempName) || databaseInterface->activeUserExists(tempName) || databaseInterface->userSessionExists(tempName)) tempName = name + "_" + QString::number(++i); name = tempName; data.set_name(name.toStdString()); diff --git a/common/server.h b/common/server.h index 896f6ef8..ad17bc53 100644 --- a/common/server.h +++ b/common/server.h @@ -7,6 +7,7 @@ #include #include #include +#include "pb/commands.pb.h" #include "pb/serverinfo_user.pb.h" #include "server_player_reference.h" @@ -27,7 +28,7 @@ class GameEventContainer; class CommandContainer; class Command_JoinGame; -enum AuthenticationResult { NotLoggedIn = 0, PasswordRight = 1, UnknownUser = 2, WouldOverwriteOldSession = 3, UserIsBanned = 4, UsernameInvalid = 5, RegistrationRequired = 6 }; +enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive }; class Server : public QObject { @@ -44,6 +45,7 @@ public: ~Server(); void setThreaded(bool _threaded) { threaded = _threaded; } AuthenticationResult loginUser(Server_ProtocolHandler *session, QString &name, const QString &password, QString &reason, int &secondsLeft); + const QMap &getRooms() { return rooms; } Server_AbstractUserInterface *findUser(const QString &userName) const; diff --git a/common/server_database_interface.h b/common/server_database_interface.h index 518bd5e6..d809b1ff 100644 --- a/common/server_database_interface.h +++ b/common/server_database_interface.h @@ -13,6 +13,8 @@ public: : QObject(parent) { } virtual AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft) = 0; + virtual bool checkUserIsBanned(const QString & /* ipAddress */, const QString & /* userName */, QString & /* banReason */, int & /* banSecondsRemaining */) { return false; } + virtual bool activeUserExists(const QString & /* user */) { return false; } virtual bool userExists(const QString & /* user */) { return false; } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); } virtual QMap getIgnoreList(const QString & /* name */) { return QMap(); } @@ -23,6 +25,7 @@ public: virtual DeckList *getDeckFromDatabase(int /* deckId */, int /* userId */) { return 0; } virtual qint64 startSession(const QString & /* userName */, const QString & /* address */) { return 0; } + virtual bool usernameIsValid(const QString & /*userName */) { return true; }; public slots: virtual void endSession(qint64 /* sessionId */ ) { } public: @@ -35,9 +38,12 @@ public: virtual bool userSessionExists(const QString & /* userName */) { return false; } virtual bool getRequireRegistration() { return false; } + virtual bool registerUser(const QString & /* userName */, const QString & /* realName */, ServerInfo_User_Gender const & /* gender */, const QString & /* password */, const QString & /* emailAddress */, const QString & /* country */, bool /* active = false */) { return false; } + virtual bool activateUser(const QString & /* userName */, const QString & /* token */) { return false; } enum LogMessage_TargetType { MessageTargetRoom, MessageTargetGame, MessageTargetChat, MessageTargetIslRoom }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, const QString & /* senderIp */, const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, const QString & /* targetName */) { }; + bool checkUserIsBanned(Server_ProtocolHandler *session, QString &banReason, int &banSecondsRemaining); }; #endif diff --git a/common/server_protocolhandler.cpp b/common/server_protocolhandler.cpp index 46d79139..46e2bb83 100644 --- a/common/server_protocolhandler.cpp +++ b/common/server_protocolhandler.cpp @@ -134,6 +134,10 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co SessionCommand debugSc(sc); debugSc.MutableExtension(Command_Login::ext)->clear_password(); logDebugMessage(QString::fromStdString(debugSc.ShortDebugString())); + } else if (num == SessionCommand::REGISTER) { + SessionCommand logSc(sc); + logSc.MutableExtension(Command_Register::ext)->clear_password(); + logDebugMessage(QString::fromStdString(logSc.ShortDebugString())); } else logDebugMessage(QString::fromStdString(sc.ShortDebugString())); } @@ -386,6 +390,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd case WouldOverwriteOldSession: return Response::RespWouldOverwriteOldSession; case UsernameInvalid: return Response::RespUsernameInvalid; case RegistrationRequired: return Response::RespRegistrationRequired; + case UserIsInactive: return Response::RespAccountNotActivated; default: authState = res; } diff --git a/common/server_protocolhandler.h b/common/server_protocolhandler.h index 5389b724..bf9d1940 100644 --- a/common/server_protocolhandler.h +++ b/common/server_protocolhandler.h @@ -28,6 +28,7 @@ class AdminCommand; class Command_Ping; class Command_Login; +class Command_Register; class Command_Message; class Command_ListUsers; class Command_GetGamesOfUser; @@ -98,6 +99,7 @@ public: void sendProtocolItem(const SessionEvent &item); void sendProtocolItem(const GameEventContainer &item); void sendProtocolItem(const RoomEvent &item); + }; #endif diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index e249fdcd..2fe46e00 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -15,6 +15,18 @@ SET(servatrice_SOURCES src/settingscache.cpp src/isl_interface.cpp ${VERSION_STRING_CPP} + src/smtp/emailaddress.cpp + src/smtp/mimeattachment.cpp + src/smtp/mimecontentformatter.cpp + src/smtp/mimefile.cpp + src/smtp/mimehtml.cpp + src/smtp/mimeinlinefile.cpp + src/smtp/mimemessage.cpp + src/smtp/mimemultipart.cpp + src/smtp/mimepart.cpp + src/smtp/mimetext.cpp + src/smtp/quotedprintable.cpp + src/smtp/smtpclient.cpp ) set(servatrice_RESOURCES servatrice.qrc) diff --git a/servatrice/scripts/.gitignore b/servatrice/scripts/.gitignore new file mode 100644 index 00000000..eb47a082 --- /dev/null +++ b/servatrice/scripts/.gitignore @@ -0,0 +1 @@ +pypb/ diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index bd1cad55..31483afe 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -54,6 +54,50 @@ password=123456 ; Accept only registered users? default is 0 (accept unregistered users) regonly=0 +[registration] + +; Servatrice can process registration requests to add new users on the fly. +; Enable this feature? Default false. +;enabled=false + +; Require users to provide an email address in order to register. Newly registered users will receive an +; activation token by email, and will be required to input back this token on cockatrice at the first login +; to get their account activated. Default true. +;requireemail=true + +[smtp] + +; Connectin type: currently supported method are "tcp", "ssl" and "tls" +connection=tcp + +; Auth type: currently supported method are "plain" and "login" +auth=plain + +; Hostname or IP addres of the smtp server +host=localhost + +; Smtp port number of the smtp server. Usual values are 25 or 587 for tcp, 465 for ssl +port=25 + +; Username: this typically matches the "from" email address +username=root@localhost + +; Password for the username +password=foobar + +; Sender email address: the "from" email address +email=root@localhost + +; Sender email name +name="Cockatrice server" + +; Email subject +subject="Cockatrice server account activation token" + +; Email body. You can use these tags here: %username %token +; They will be substituted with the actual values in the email +; +body="Hi %username, thank our for registering on our Cockatrice server\r\nHere's the activation token you need to supply for activatin your account:\r\n\r\n%token\r\n\r\nHappy gaming!" [database] diff --git a/servatrice/src/passwordhasher.cpp b/servatrice/src/passwordhasher.cpp index 554c8f2f..41e73b9e 100644 --- a/servatrice/src/passwordhasher.cpp +++ b/servatrice/src/passwordhasher.cpp @@ -4,10 +4,11 @@ #include #include #include -#else - #include #endif +#include +#include "rng_sfmt.h" + void PasswordHasher::initialize() { #if QT_VERSION < 0x050000 @@ -51,3 +52,25 @@ QString PasswordHasher::computeHash(const QString &password, const QString &salt return hashedPass; } #endif + +QString PasswordHasher::generateRandomSalt(const int len) +{ + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + QString ret; + int size = sizeof(alphanum) - 1; + + for (int i = 0; i < len; ++i) { + ret.append(alphanum[rng->rand(0, size)]); + } + + return ret; +} + +QString PasswordHasher::generateActivationToken() +{ + return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16); +} \ No newline at end of file diff --git a/servatrice/src/passwordhasher.h b/servatrice/src/passwordhasher.h index 0cb6744c..4160cb0f 100644 --- a/servatrice/src/passwordhasher.h +++ b/servatrice/src/passwordhasher.h @@ -7,6 +7,8 @@ class PasswordHasher { public: static void initialize(); static QString computeHash(const QString &password, const QString &salt); + static QString generateRandomSalt(const int len = 16); + static QString generateActivationToken(); }; #endif diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index 66a9c977..ad6f6ada 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -160,6 +160,13 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationNone; } + bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); + bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); + + qDebug() << "Registration enabled: " << registrationEnabled; + if (registrationEnabled) + qDebug() << "Require email address to register: " << requireEmailForRegistration; + QString dbTypeStr = settingsCache->value("database/type").toString(); if (dbTypeStr == "mysql") databaseType = DatabaseMySql; @@ -172,12 +179,17 @@ bool Servatrice::initServer() if (databaseType != DatabaseNone) { settingsCache->beginGroup("database"); dbPrefix = settingsCache->value("prefix").toString(); - servatriceDatabaseInterface->initDatabase("QMYSQL", - settingsCache->value("hostname").toString(), - settingsCache->value("database").toString(), - settingsCache->value("user").toString(), - settingsCache->value("password").toString()); + bool dbOpened = + servatriceDatabaseInterface->initDatabase("QMYSQL", + settingsCache->value("hostname").toString(), + settingsCache->value("database").toString(), + settingsCache->value("user").toString(), + settingsCache->value("password").toString()); settingsCache->endGroup(); + if (!dbOpened) { + qDebug() << "Failed to open database"; + return false; + } updateServerList(); @@ -342,7 +354,7 @@ bool Servatrice::initServer() if (gameServer->listen(QHostAddress::Any, gamePort)) qDebug() << "Server listening."; else { - qDebug() << "gameServer->listen(): Error."; + qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); return false; } return true; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index 77f31156..b2027709 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -9,6 +9,7 @@ #include #include #include +#include Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), @@ -34,7 +35,9 @@ void Servatrice_DatabaseInterface::initDatabase(const QSqlDatabase &_sqlDatabase } } -void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password) +bool Servatrice_DatabaseInterface::initDatabase(const QString &type, const QString &hostName, + const QString &databaseName, const QString &userName, + const QString &password) { sqlDatabase = QSqlDatabase::addDatabase(type, "main"); sqlDatabase.setHostName(hostName); @@ -42,7 +45,7 @@ void Servatrice_DatabaseInterface::initDatabase(const QString &type, const QStri sqlDatabase.setUserName(userName); sqlDatabase.setPassword(password); - openDatabase(); + return openDatabase(); } bool Servatrice_DatabaseInterface::openDatabase() @@ -102,11 +105,89 @@ bool Servatrice_DatabaseInterface::usernameIsValid(const QString &user) return re.exactMatch(user); } +// TODO move this to Server bool Servatrice_DatabaseInterface::getRequireRegistration() { return settingsCache->value("authentication/regonly", 0).toBool(); } +bool Servatrice_DatabaseInterface::registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active) +{ + if (!checkSql()) + return false; + + QString passwordSha512 = PasswordHasher::computeHash(password, PasswordHasher::generateRandomSalt()); + token = active ? QString() : PasswordHasher::generateActivationToken(); + + QSqlQuery *query = prepareQuery("insert into {prefix}_users " + "(name, realname, gender, password_sha512, email, country, registrationDate, active, token) " + "values " + "(:userName, :realName, :gender, :password_sha512, :email, :country, UTC_TIMESTAMP(), :active, :token)"); + query->bindValue(":userName", userName); + query->bindValue(":realName", realName); + query->bindValue(":gender", getGenderChar(gender)); + query->bindValue(":password_sha512", passwordSha512); + query->bindValue(":email", emailAddress); + query->bindValue(":country", country); + query->bindValue(":active", active ? 1 : 0); + query->bindValue(":token", token); + + if (!execSqlQuery(query)) { + qDebug() << "Failed to insert user: " << query->lastError() << " sql: " << query->lastQuery(); + return false; + } + + return true; +} + +bool Servatrice_DatabaseInterface::activateUser(const QString &userName, const QString &token) +{ + if (!checkSql()) + return false; + + QSqlQuery *activateQuery = prepareQuery("select name from {prefix}_users where active=0 and name=:username and token=:token"); + + activateQuery->bindValue(":username", userName); + activateQuery->bindValue(":token", token); + if (!execSqlQuery(activateQuery)) { + qDebug() << "Account activation failed: SQL error." << activateQuery->lastError()<< " sql: " << activateQuery->lastQuery(); + return false; + } + + if (activateQuery->next()) { + const QString name = activateQuery->value(0).toString(); + // redundant check + if(name == userName) + { + + QSqlQuery *query = prepareQuery("update {prefix}_users set active=1 where name = :userName"); + query->bindValue(":userName", userName); + + if (!execSqlQuery(query)) { + qDebug() << "Failed to activate user: " << query->lastError() << " sql: " << query->lastQuery(); + return false; + } + + return true; + } + } + return false; +} + +QChar Servatrice_DatabaseInterface::getGenderChar(ServerInfo_User_Gender const &gender) +{ + switch (gender) { + case ServerInfo_User_Gender_GenderUnknown: + return QChar('u'); + case ServerInfo_User_Gender_Male: + return QChar('m'); + case ServerInfo_User_Gender_Female: + return QChar('f'); + default: + return QChar('u'); + } +} + AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &banSecondsLeft) { switch (server->getAuthenticationMethod()) { @@ -125,45 +206,10 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (!usernameIsValid(user)) return UsernameInvalid; - QSqlQuery *ipBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.ip_address = :address) and b.ip_address = :address2"); - ipBanQuery->bindValue(":address", static_cast(handler)->getPeerAddress().toString()); - ipBanQuery->bindValue(":address2", static_cast(handler)->getPeerAddress().toString()); - if (!execSqlQuery(ipBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } + if (checkUserIsBanned(handler->getAddress(), user, reasonStr, banSecondsLeft)) + return UserIsBanned; - if (ipBanQuery->next()) { - const int secondsLeft = -ipBanQuery->value(0).toInt(); - const bool permanentBan = ipBanQuery->value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = ipBanQuery->value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by address"); - return UserIsBanned; - } - } - - QSqlQuery *nameBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); - nameBanQuery->bindValue(":name1", user); - nameBanQuery->bindValue(":name2", user); - if (!execSqlQuery(nameBanQuery)) { - qDebug("Login denied: SQL error"); - return NotLoggedIn; - } - - if (nameBanQuery->next()) { - const int secondsLeft = -nameBanQuery->value(0).toInt(); - const bool permanentBan = nameBanQuery->value(1).toInt(); - if ((secondsLeft > 0) || permanentBan) { - reasonStr = nameBanQuery->value(2).toString(); - banSecondsLeft = permanentBan ? 0 : secondsLeft; - qDebug("Login denied: banned by name"); - return UserIsBanned; - } - } - - QSqlQuery *passwordQuery = prepareQuery("select password_sha512 from {prefix}_users where name = :name and active = 1"); + QSqlQuery *passwordQuery = prepareQuery("select password_sha512, active from {prefix}_users where name = :name"); passwordQuery->bindValue(":name", user); if (!execSqlQuery(passwordQuery)) { qDebug("Login denied: SQL error"); @@ -172,6 +218,11 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot if (passwordQuery->next()) { const QString correctPassword = passwordQuery->value(0).toString(); + const bool userIsActive = passwordQuery->value(1).toBool(); + if(!userIsActive) { + qDebug("Login denied: user not active"); + return UserIsInactive; + } if (correctPassword == PasswordHasher::computeHash(password, correctPassword.left(16))) { qDebug("Login accepted: password right"); return PasswordRight; @@ -188,7 +239,80 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot return UnknownUser; } -bool Servatrice_DatabaseInterface::userExists(const QString &user) +bool Servatrice_DatabaseInterface::checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) + return false; + + if (!checkSql()) { + qDebug("Failed to check if user is banned. Database invalid."); + return false; + } + + return + checkUserIsIpBanned(ipAddress, banReason, banSecondsRemaining) + || checkUserIsNameBanned(userName, banReason, banSecondsRemaining); + +} + +bool Servatrice_DatabaseInterface::checkUserIsNameBanned(const QString &userName, QString &banReason, int &banSecondsRemaining) +{ + QSqlQuery *nameBanQuery = prepareQuery("select time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute))), b.minutes <=> 0, b.visible_reason from {prefix}_bans b where b.time_from = (select max(c.time_from) from {prefix}_bans c where c.user_name = :name2) and b.user_name = :name1"); + nameBanQuery->bindValue(":name1", userName); + nameBanQuery->bindValue(":name2", userName); + if (!execSqlQuery(nameBanQuery)) { + qDebug() << "Name ban check failed: SQL error" << nameBanQuery->lastError(); + return false; + } + + if (nameBanQuery->next()) { + const int secondsLeft = -nameBanQuery->value(0).toInt(); + const bool permanentBan = nameBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = nameBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "Username" << userName << "is banned by name"; + return true; + } + } + return false; +} + +bool Servatrice_DatabaseInterface::checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining) +{ + QSqlQuery *ipBanQuery = prepareQuery( + "select" + " time_to_sec(timediff(now(), date_add(b.time_from, interval b.minutes minute)))," + " b.minutes <=> 0," + " b.visible_reason" + " from {prefix}_bans b" + " where" + " b.time_from = (select max(c.time_from)" + " from {prefix}_bans c" + " where c.ip_address = :address)" + " and b.ip_address = :address2"); + + ipBanQuery->bindValue(":address", ipAddress); + ipBanQuery->bindValue(":address2", ipAddress); + if (!execSqlQuery(ipBanQuery)) { + qDebug() << "IP ban check failed: SQL error." << ipBanQuery->lastError(); + return false; + } + + if (ipBanQuery->next()) { + const int secondsLeft = -ipBanQuery->value(0).toInt(); + const bool permanentBan = ipBanQuery->value(1).toInt(); + if ((secondsLeft > 0) || permanentBan) { + banReason = ipBanQuery->value(2).toString(); + banSecondsRemaining = permanentBan ? 0 : secondsLeft; + qDebug() << "User is banned by address" << ipAddress; + return true; + } + } + return false; +} + +bool Servatrice_DatabaseInterface::activeUserExists(const QString &user) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { checkSql(); @@ -202,6 +326,20 @@ bool Servatrice_DatabaseInterface::userExists(const QString &user) return false; } +bool Servatrice_DatabaseInterface::userExists(const QString &user) +{ + if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + checkSql(); + + QSqlQuery *query = prepareQuery("select 1 from {prefix}_users where name = :name"); + query->bindValue(":name", user); + if (!execSqlQuery(query)) + return false; + return query->next(); + } + return false; +} + int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { @@ -579,4 +717,4 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, const QString query->bindValue(":target_id", (targetType == MessageTargetChat && targetId < 1) ? QVariant() : targetId); query->bindValue(":target_name", targetName); execSqlQuery(query); -} +} \ No newline at end of file diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 8850d7be..d7715544 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -4,6 +4,7 @@ #include #include #include +#include #include "server.h" #include "server_database_interface.h" @@ -18,7 +19,11 @@ private: QHash preparedStatements; Servatrice *server; ServerInfo_User evalUserQueryResult(const QSqlQuery *query, bool complete, bool withId = false); - bool usernameIsValid(const QString &user); + /** 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); + QChar getGenderChar(ServerInfo_User_Gender const &gender); protected: AuthenticationResult checkUserPassword(Server_ProtocolHandler *handler, const QString &user, const QString &password, QString &reasonStr, int &secondsLeft); public slots: @@ -26,13 +31,15 @@ public slots: public: Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server); ~Servatrice_DatabaseInterface(); - void initDatabase(const QString &type, const QString &hostName, const QString &databaseName, const QString &userName, const QString &password); + 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); bool userExists(const QString &user); int getUserIdInDB(const QString &name); QMap getBuddyList(const QString &name); @@ -53,8 +60,12 @@ public: void lockSessionTables(); void unlockSessionTables(); bool userSessionExists(const QString &userName); + bool usernameIsValid(const QString &user); + bool checkUserIsBanned(const QString &ipAddress, const QString &userName, QString &banReason, int &banSecondsRemaining); bool getRequireRegistration(); + bool registerUser(const QString &userName, const QString &realName, ServerInfo_User_Gender const &gender, const QString &password, const QString &emailAddress, const QString &country, QString &token, bool active = false); + bool activateUser(const QString &userName, const QString &token); void logMessage(const int senderId, const QString &senderName, const QString &senderIp, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, const QString &targetName); }; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index 86485661..a2fa26b2 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -51,12 +51,15 @@ #include "pb/response_deck_list.pb.h" #include "pb/response_deck_download.pb.h" #include "pb/response_deck_upload.pb.h" +#include "pb/response_register.pb.h" #include "pb/response_replay_list.pb.h" #include "pb/response_replay_download.pb.h" #include "pb/serverinfo_replay.pb.h" #include "pb/serverinfo_user.pb.h" #include "pb/serverinfo_deckstorage.pb.h" +#include "smtp/SmtpMime" + #include "version_string.h" #include #include @@ -267,6 +270,8 @@ Response::ResponseCode ServerSocketInterface::processExtendedSessionCommand(int case SessionCommand::REPLAY_DOWNLOAD: return cmdReplayDownload(cmd.GetExtension(Command_ReplayDownload::ext), rc); case SessionCommand::REPLAY_MODIFY_MATCH: return cmdReplayModifyMatch(cmd.GetExtension(Command_ReplayModifyMatch::ext), rc); case SessionCommand::REPLAY_DELETE_MATCH: return cmdReplayDeleteMatch(cmd.GetExtension(Command_ReplayDeleteMatch::ext), rc); + case SessionCommand::REGISTER: return cmdRegisterAccount(cmd.GetExtension(Command_Register::ext), rc); break; + case SessionCommand::ACTIVATE: return cmdActivateAccount(cmd.GetExtension(Command_Activate::ext), rc); break; default: return Response::RespFunctionNotAllowed; } } @@ -764,6 +769,179 @@ Response::ResponseCode ServerSocketInterface::cmdBanFromServer(const Command_Ban return Response::RespOk; } +Response::ResponseCode ServerSocketInterface::cmdRegisterAccount(const Command_Register &cmd, ResponseContainer &rc) +{ + QString userName = QString::fromStdString(cmd.user_name()); + qDebug() << "Got register command: " << userName; + + bool registrationEnabled = settingsCache->value("registration/enabled", false).toBool(); + if (!registrationEnabled) + return Response::RespRegistrationDisabled; + + QString emailAddress = QString::fromStdString(cmd.email()); + bool requireEmailForRegistration = settingsCache->value("registration/requireemail", true).toBool(); + if (requireEmailForRegistration) + { + QRegExp rx("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}\\b"); + if(emailAddress.isEmpty() || !rx.exactMatch(emailAddress)) + return Response::RespEmailRequiredToRegister; + } + + // TODO: Move this method outside of the db interface + if (!sqlInterface->usernameIsValid(userName)) + return Response::RespUsernameInvalid; + + if(sqlInterface->userExists(userName)) + return Response::RespUserAlreadyExists; + + QString banReason; + int banSecondsRemaining; + if (sqlInterface->checkUserIsBanned(this->getAddress(), userName, banReason, banSecondsRemaining)) + { + Response_Register *re = new Response_Register; + re->set_denied_reason_str(banReason.toStdString()); + if (banSecondsRemaining != 0) + re->set_denied_end_time(QDateTime::currentDateTime().addSecs(banSecondsRemaining).toTime_t()); + rc.setResponseExtension(re); + return Response::RespUserIsBanned; + } + + if (tooManyRegistrationAttempts(this->getAddress())) + return Response::RespTooManyRequests; + + QString realName = QString::fromStdString(cmd.real_name()); + ServerInfo_User_Gender gender = cmd.gender(); + QString country = QString::fromStdString(cmd.country()); + QString password = QString::fromStdString(cmd.password()); + + // TODO make this configurable? + if(password.length() < 6) + return Response::RespPasswordTooShort; + + QString token; + bool regSucceeded = sqlInterface->registerUser(userName, realName, gender, password, emailAddress, country, token, !requireEmailForRegistration); + + if(regSucceeded) + { + qDebug() << "Accepted register command for user: " << userName; + if(requireEmailForRegistration) + { + // TODO call a slot on another thread to send email + sendActivationTokenMail(userName, emailAddress, token); + return Response::RespRegistrationAcceptedNeedsActivation; + } else { + return Response::RespRegistrationAccepted; + } + } else { + return Response::RespRegistrationFailed; + } +} + +bool ServerSocketInterface::sendActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token) +{ + QString tmp = settingsCache->value("smtp/connection", "tcp").toString(); + SmtpClient::ConnectionType connection = SmtpClient::TcpConnection; + if(tmp == "ssl") + connection = SmtpClient::SslConnection; + else if(tmp == "tls") + connection = SmtpClient::TlsConnection; + + tmp = settingsCache->value("smtp/auth", "plain").toString(); + SmtpClient::AuthMethod auth = SmtpClient::AuthPlain; + if(tmp == "login") + auth = SmtpClient::AuthLogin; + + QString host = settingsCache->value("smtp/host", "localhost").toString(); + int port = settingsCache->value("smtp/port", 25).toInt(); + QString username = settingsCache->value("smtp/username", "").toString(); + QString password = settingsCache->value("smtp/password", "").toString(); + 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 email field" << endl; + return false; + } + if(body.isEmpty()) + { + qDebug() << "[MAIL] Missing body field" << endl; + return false; + } + if(recipient.isEmpty()) + { + qDebug() << "[MAIL] Missing recipient field" << endl; + return false; + } + if(token.isEmpty()) + { + qDebug() << "[MAIL] Missing token field" << endl; + return false; + } + + SmtpClient smtp(host, port, connection); + smtp.setUser(username); + smtp.setPassword(password); + smtp.setAuthMethod(auth); + + MimeMessage message; + EmailAddress sender(email, name); + message.setSender(&sender); + EmailAddress to(recipient, nickname); + message.addRecipient(&to); + message.setSubject(subject); + + MimeText text; + text.setText(body.replace("%username", nickname).replace("%token", token)); + message.addPart(&text); + + // Now we can send the mail + + if (!smtp.connectToHost()) { + qDebug() << "[MAIL] Failed to connect to host" << host << "on port" << port; + return false; + } + + if (!smtp.login()) { + qDebug() << "[MAIL] Failed to login as " << username; + return false; + } + + if (!smtp.sendMail(message)) { + qDebug() << "[MAIL] Failed to send mail to " << recipient; + return false; + } + + smtp.quit(); + qDebug() << "[MAIL] Sent activation email to " << recipient << " for nickname " << nickname; + return true; +} + +bool ServerSocketInterface::tooManyRegistrationAttempts(const QString &ipAddress) +{ + // TODO: implement + Q_UNUSED(ipAddress); + return false; +} + +Response::ResponseCode ServerSocketInterface::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /*rc*/) +{ + QString userName = QString::fromStdString(cmd.user_name()); + QString token = QString::fromStdString(cmd.token()); + + if(sqlInterface->activateUser(userName, token)) + { + qDebug() << "Accepted activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationAccepted; + } else { + qDebug() << "Failed activation for user" << QString::fromStdString(cmd.user_name()); + return Response::RespActivationFailed; + } +} + + // ADMIN FUNCTIONS. // Permission is checked by the calling function. diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 6a2a41f8..c89709d3 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -59,6 +59,7 @@ signals: void outputQueueChanged(); protected: void logDebugMessage(const QString &message); + bool tooManyRegistrationAttempts(const QString &ipAddress); private: QMutex outputQueueMutex; Servatrice *servatrice; @@ -91,10 +92,14 @@ private: Response::ResponseCode cmdBanFromServer(const Command_BanFromServer &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 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); + + bool sendActivationTokenMail(const QString &nickname, const QString &recipient, const QString &token); public: ServerSocketInterface(Servatrice *_server, Servatrice_DatabaseInterface *_databaseInterface, QObject *parent = 0); ~ServerSocketInterface(); diff --git a/servatrice/src/smtp/SmtpMime b/servatrice/src/smtp/SmtpMime new file mode 100644 index 00000000..940996b8 --- /dev/null +++ b/servatrice/src/smtp/SmtpMime @@ -0,0 +1,31 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef SMTPMIME_H +#define SMTPMIME_H + +#include "smtpclient.h" +#include "mimepart.h" +#include "mimehtml.h" +#include "mimeattachment.h" +#include "mimemessage.h" +#include "mimetext.h" +#include "mimeinlinefile.h" +#include "mimefile.h" + +#endif // SMTPMIME_H diff --git a/servatrice/src/smtp/emailaddress.cpp b/servatrice/src/smtp/emailaddress.cpp new file mode 100644 index 00000000..9480e5d4 --- /dev/null +++ b/servatrice/src/smtp/emailaddress.cpp @@ -0,0 +1,60 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "emailaddress.h" + +/* [1] Constructors and Destructors */ + +EmailAddress::EmailAddress(const QString & address, const QString & name) +{ + this->address = address; + this->name = name; +} + +EmailAddress::~EmailAddress() +{ +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void EmailAddress::setName(const QString & name) +{ + this->name = name; + +} + +void EmailAddress::setAddress(const QString & address) +{ + this->address = address; +} + +const QString & EmailAddress::getName() const +{ + return name; +} + +const QString & EmailAddress::getAddress() const +{ + return address; +} + +/* [2] --- */ + diff --git a/servatrice/src/smtp/emailaddress.h b/servatrice/src/smtp/emailaddress.h new file mode 100644 index 00000000..4ecf6efc --- /dev/null +++ b/servatrice/src/smtp/emailaddress.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef EMAILADDRESS_H +#define EMAILADDRESS_H + +#include + +#include "smtpexports.h" + +class SMTP_EXPORT EmailAddress : public QObject +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + EmailAddress(); + EmailAddress(const QString & address, const QString & name=""); + + ~EmailAddress(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + void setName(const QString & name); + void setAddress(const QString & address); + + const QString & getName() const; + const QString & getAddress() const; + + /* [2] --- */ + + +private: + + /* [3] Private members */ + + QString name; + QString address; + + /* [3] --- */ +}; + +#endif // EMAILADDRESS_H diff --git a/servatrice/src/smtp/mimeattachment.cpp b/servatrice/src/smtp/mimeattachment.cpp new file mode 100644 index 00000000..0dea6222 --- /dev/null +++ b/servatrice/src/smtp/mimeattachment.cpp @@ -0,0 +1,50 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimeattachment.h" +#include + +/* [1] Constructors and Destructors */ + +MimeAttachment::MimeAttachment(QFile *file) + : MimeFile(file) +{ +} +MimeAttachment::MimeAttachment(const QByteArray& stream, const QString& fileName): MimeFile(stream, fileName) +{ + +} + +MimeAttachment::~MimeAttachment() +{ +} + +/* [1] --- */ + + +/* [2] Protected methods */ + +void MimeAttachment::prepare() +{ + this->header += "Content-disposition: attachment\r\n"; + + /* !!! IMPORTANT !!! */ + MimeFile::prepare(); +} + +/* [2] --- */ diff --git a/servatrice/src/smtp/mimeattachment.h b/servatrice/src/smtp/mimeattachment.h new file mode 100644 index 00000000..4a92e9a1 --- /dev/null +++ b/servatrice/src/smtp/mimeattachment.h @@ -0,0 +1,51 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEATTACHMENT_H +#define MIMEATTACHMENT_H + +#include +#include "mimepart.h" +#include "mimefile.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeAttachment : public MimeFile +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeAttachment(QFile* file); + MimeAttachment(const QByteArray& stream, const QString& fileName); + + ~MimeAttachment(); + + /* [1] --- */ + +protected: + + /* [2] Protected methods */ + + virtual void prepare(); + + /* [2] --- */ +}; + +#endif // MIMEATTACHMENT_H diff --git a/servatrice/src/smtp/mimecontentformatter.cpp b/servatrice/src/smtp/mimecontentformatter.cpp new file mode 100644 index 00000000..7f5a6e43 --- /dev/null +++ b/servatrice/src/smtp/mimecontentformatter.cpp @@ -0,0 +1,66 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimecontentformatter.h" + +MimeContentFormatter::MimeContentFormatter(int max_length) : + max_length(max_length) +{} + +QString MimeContentFormatter::format(const QString &content, bool quotedPrintable) const { + + QString out; + + int chars = 0; + for (int i = 0; i < content.length() ; ++i) { + chars++; + if (!quotedPrintable) { + if (chars > max_length) { + out.append("\r\n"); + chars = 1; + } + } + else { + if (content[i] == '\n') { // new line + out.append(content[i]); + chars = 0; + continue; + } + + if ((chars > max_length - 1) + || ((content[i] == '=') && (chars > max_length - 3) )) { + out.append('='); + out.append("\r\n"); + chars = 1; + } + + } + out.append(content[i]); + } + + return out; + +} + +void MimeContentFormatter::setMaxLength(int l) { + max_length = l; +} + +int MimeContentFormatter::getMaxLength() const { + return max_length; +} diff --git a/servatrice/src/smtp/mimecontentformatter.h b/servatrice/src/smtp/mimecontentformatter.h new file mode 100644 index 00000000..bf751588 --- /dev/null +++ b/servatrice/src/smtp/mimecontentformatter.h @@ -0,0 +1,43 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMECONTENTFORMATTER_H +#define MIMECONTENTFORMATTER_H + +#include +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeContentFormatter : public QObject +{ + Q_OBJECT +public: + MimeContentFormatter (int max_length = 76); + + void setMaxLength(int l); + int getMaxLength() const; + + QString format(const QString &content, bool quotedPrintable = false) const; + +protected: + int max_length; + +}; + +#endif // MIMECONTENTFORMATTER_H diff --git a/servatrice/src/smtp/mimefile.cpp b/servatrice/src/smtp/mimefile.cpp new file mode 100644 index 00000000..c6f313a8 --- /dev/null +++ b/servatrice/src/smtp/mimefile.cpp @@ -0,0 +1,70 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimefile.h" +#include + +/* [1] Constructors and Destructors */ + +MimeFile::MimeFile(QFile *file) +{ + this->file = file; + this->cType = "application/octet-stream"; + this->cName = QFileInfo(*file).fileName(); + this->cEncoding = Base64; +} + +MimeFile::MimeFile(const QByteArray& stream, const QString& fileName) +{ + this->cEncoding = Base64; + this->cType = "application/octet-stream"; + this->file = 0; + this->cName = fileName; + this->content = stream; +} + +MimeFile::~MimeFile() +{ + if (file) + delete file; +} + +/* [1] --- */ + + +/* [2] Getters and setters */ + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeFile::prepare() +{ + if (this->file) + { + file->open(QIODevice::ReadOnly); + this->content = file->readAll(); + file->close(); + } + /* !!! IMPORTANT !!!! */ + MimePart::prepare(); +} + +/* [3] --- */ + diff --git a/servatrice/src/smtp/mimefile.h b/servatrice/src/smtp/mimefile.h new file mode 100644 index 00000000..13f444de --- /dev/null +++ b/servatrice/src/smtp/mimefile.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEFILE_H +#define MIMEFILE_H + +#include "mimepart.h" +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeFile : public MimePart +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeFile(const QByteArray& stream, const QString& fileName); + MimeFile(QFile *f); + ~MimeFile(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + QFile* file; + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ + +}; + +#endif // MIMEFILE_H diff --git a/servatrice/src/smtp/mimehtml.cpp b/servatrice/src/smtp/mimehtml.cpp new file mode 100644 index 00000000..5594d3a4 --- /dev/null +++ b/servatrice/src/smtp/mimehtml.cpp @@ -0,0 +1,57 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimehtml.h" + +/* [1] Constructors and Destructors */ + +MimeHtml::MimeHtml(const QString &html) : MimeText(html) +{ + this->cType = "text/html"; +} + +MimeHtml::~MimeHtml() {} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimeHtml::setHtml(const QString & html) +{ + this->text = html; +} + +const QString & MimeHtml::getHtml() const +{ + return text; +} + + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeHtml::prepare() +{ + /* !!! IMPORTANT !!! */ + MimeText::prepare(); +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimehtml.h b/servatrice/src/smtp/mimehtml.h new file mode 100644 index 00000000..f3f54300 --- /dev/null +++ b/servatrice/src/smtp/mimehtml.h @@ -0,0 +1,61 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEHTML_H +#define MIMEHTML_H + +#include "mimetext.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeHtml : public MimeText +{ + Q_OBJECT +public: + + /* [1] Constructors and Destructors */ + + MimeHtml(const QString &html = ""); + ~MimeHtml(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + void setHtml(const QString & html); + + const QString& getHtml() const; + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ +}; + +#endif // MIMEHTML_H diff --git a/servatrice/src/smtp/mimeinlinefile.cpp b/servatrice/src/smtp/mimeinlinefile.cpp new file mode 100644 index 00000000..0823b0db --- /dev/null +++ b/servatrice/src/smtp/mimeinlinefile.cpp @@ -0,0 +1,52 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimeinlinefile.h" + +/* [1] Constructors and Destructors */ + +MimeInlineFile::MimeInlineFile(QFile *f) + : MimeFile(f) +{ +} + +MimeInlineFile::~MimeInlineFile() +{} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +/* [2] --- */ + + +/* [3] Protected methods */ + +void MimeInlineFile::prepare() +{ + this->header += "Content-Disposition: inline\r\n"; + + /* !!! IMPORTANT !!! */ + MimeFile::prepare(); +} + +/* [3] --- */ + + + diff --git a/servatrice/src/smtp/mimeinlinefile.h b/servatrice/src/smtp/mimeinlinefile.h new file mode 100644 index 00000000..98fe3cab --- /dev/null +++ b/servatrice/src/smtp/mimeinlinefile.h @@ -0,0 +1,56 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEINLINEFILE_H +#define MIMEINLINEFILE_H + +#include "mimefile.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeInlineFile : public MimeFile +{ +public: + + /* [1] Constructors and Destructors */ + + MimeInlineFile(QFile *f); + ~MimeInlineFile(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + /* [3] --- */ + + + /* [4] Protected methods */ + + virtual void prepare(); + + /* [4] --- */ +}; + +#endif // MIMEINLINEFILE_H diff --git a/servatrice/src/smtp/mimemessage.cpp b/servatrice/src/smtp/mimemessage.cpp new file mode 100644 index 00000000..0c4ebc2d --- /dev/null +++ b/servatrice/src/smtp/mimemessage.cpp @@ -0,0 +1,257 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimemessage.h" + +#include +#include "quotedprintable.h" +#include + +/* [1] Constructors and Destructors */ +MimeMessage::MimeMessage(bool createAutoMimeContent) : + hEncoding(MimePart::_8Bit) +{ + if (createAutoMimeContent) + this->content = new MimeMultiPart(); + + autoMimeContentCreated = createAutoMimeContent; +} + +MimeMessage::~MimeMessage() +{ + if (this->autoMimeContentCreated) + { + this->autoMimeContentCreated = false; + delete (this->content); + } +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ +MimePart& MimeMessage::getContent() { + return *content; +} + +void MimeMessage::setContent(MimePart *content) { + if (this->autoMimeContentCreated) + { + this->autoMimeContentCreated = false; + delete (this->content); + } + this->content = content; +} + +void MimeMessage::setSender(EmailAddress* e) +{ + this->sender = e; +} + +void MimeMessage::addRecipient(EmailAddress* rcpt, RecipientType type) +{ + switch (type) + { + case To: + recipientsTo << rcpt; + break; + case Cc: + recipientsCc << rcpt; + break; + case Bcc: + recipientsBcc << rcpt; + break; + } +} + +void MimeMessage::addTo(EmailAddress* rcpt) { + this->recipientsTo << rcpt; +} + +void MimeMessage::addCc(EmailAddress* rcpt) { + this->recipientsCc << rcpt; +} + +void MimeMessage::addBcc(EmailAddress* rcpt) { + this->recipientsBcc << rcpt; +} + +void MimeMessage::setSubject(const QString & subject) +{ + this->subject = subject; +} + +void MimeMessage::addPart(MimePart *part) +{ + if (typeid(*content) == typeid(MimeMultiPart)) { + ((MimeMultiPart*) content)->addPart(part); + }; +} + +void MimeMessage::setHeaderEncoding(MimePart::Encoding hEnc) +{ + this->hEncoding = hEnc; +} + +const EmailAddress & MimeMessage::getSender() const +{ + return *sender; +} + +const QList & MimeMessage::getRecipients(RecipientType type) const +{ + switch (type) + { + default: + case To: + return recipientsTo; + case Cc: + return recipientsCc; + case Bcc: + return recipientsBcc; + } +} + +const QString & MimeMessage::getSubject() const +{ + return subject; +} + +const QList & MimeMessage::getParts() const +{ + if (typeid(*content) == typeid(MimeMultiPart)) { + return ((MimeMultiPart*) content)->getParts(); + } + else { + QList *res = new QList(); + res->append(content); + return *res; + } +} + +/* [2] --- */ + + +/* [3] Public Methods */ + +QString MimeMessage::toString() +{ + QString mime; + + /* =========== MIME HEADER ============ */ + + /* ---------- Sender / From ----------- */ + mime = "From:"; + if (sender->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append(sender->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append(sender->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + sender->getName(); + } + } + mime += " <" + sender->getAddress() + ">\r\n"; + /* ---------------------------------- */ + + + /* ------- Recipients / To ---------- */ + mime += "To:"; + QList::iterator it; int i; + for (i = 0, it = recipientsTo.begin(); it != recipientsTo.end(); ++it, ++i) + { + if (i != 0) { mime += ","; } + + if ((*it)->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append((*it)->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append((*it)->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + (*it)->getName(); + } + } + mime += " <" + (*it)->getAddress() + ">"; + } + mime += "\r\n"; + /* ---------------------------------- */ + + /* ------- Recipients / Cc ---------- */ + if (recipientsCc.size() != 0) { + mime += "Cc:"; + } + for (i = 0, it = recipientsCc.begin(); it != recipientsCc.end(); ++it, ++i) + { + if (i != 0) { mime += ","; } + + if ((*it)->getName() != "") + { + switch (hEncoding) + { + case MimePart::Base64: + mime += " =?utf-8?B?" + QByteArray().append((*it)->getName()).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += " =?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append((*it)->getName())).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += " " + (*it)->getName(); + } + } + mime += " <" + (*it)->getAddress() + ">"; + } + if (recipientsCc.size() != 0) { + mime += "\r\n"; + } + /* ---------------------------------- */ + + /* ------------ Subject ------------- */ + mime += "Subject: "; + + + switch (hEncoding) + { + case MimePart::Base64: + mime += "=?utf-8?B?" + QByteArray().append(subject).toBase64() + "?="; + break; + case MimePart::QuotedPrintable: + mime += "=?utf-8?Q?" + QuotedPrintable::encode(QByteArray().append(subject)).replace(' ', "_").replace(':',"=3A") + "?="; + break; + default: + mime += subject; + } + /* ---------------------------------- */ + + mime += "\r\n"; + mime += "MIME-Version: 1.0\r\n"; + + mime += content->toString(); + return mime; +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimemessage.h b/servatrice/src/smtp/mimemessage.h new file mode 100644 index 00000000..18b4ec3b --- /dev/null +++ b/servatrice/src/smtp/mimemessage.h @@ -0,0 +1,92 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEMESSAGE_H +#define MIMEMESSAGE_H + +#include "mimepart.h" +#include "mimemultipart.h" +#include "emailaddress.h" +#include + +#include "smtpexports.h" + +class SMTP_EXPORT MimeMessage : public QObject +{ +public: + + enum RecipientType { + To, // primary + Cc, // carbon copy + Bcc // blind carbon copy + }; + + /* [1] Constructors and Destructors */ + + MimeMessage(bool createAutoMimeConent = true); + ~MimeMessage(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + void setSender(EmailAddress* e); + void addRecipient(EmailAddress* rcpt, RecipientType type = To); + void addTo(EmailAddress* rcpt); + void addCc(EmailAddress* rcpt); + void addBcc(EmailAddress* rcpt); + void setSubject(const QString & subject); + void addPart(MimePart* part); + + void setHeaderEncoding(MimePart::Encoding); + + const EmailAddress & getSender() const; + const QList & getRecipients(RecipientType type = To) const; + const QString & getSubject() const; + const QList & getParts() const; + + MimePart& getContent(); + void setContent(MimePart *content); + /* [2] --- */ + + + /* [3] Public methods */ + + virtual QString toString(); + + /* [3] --- */ + +protected: + + /* [4] Protected members */ + + EmailAddress* sender; + QList recipientsTo, recipientsCc, recipientsBcc; + QString subject; + MimePart *content; + bool autoMimeContentCreated; + + MimePart::Encoding hEncoding; + + /* [4] --- */ + + +}; + +#endif // MIMEMESSAGE_H diff --git a/servatrice/src/smtp/mimemultipart.cpp b/servatrice/src/smtp/mimemultipart.cpp new file mode 100644 index 00000000..19d47297 --- /dev/null +++ b/servatrice/src/smtp/mimemultipart.cpp @@ -0,0 +1,78 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimemultipart.h" +#include +#include + +const QString MULTI_PART_NAMES[] = { + "multipart/mixed", // Mixed + "multipart/digest", // Digest + "multipart/alternative", // Alternative + "multipart/related", // Related + "multipart/report", // Report + "multipart/signed", // Signed + "multipart/encrypted" // Encrypted +}; + +MimeMultiPart::MimeMultiPart(MultiPartType type) +{ + this->type = type; + this->cType = MULTI_PART_NAMES[this->type]; + this->cEncoding = _8Bit; + + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(QByteArray().append(qrand())); + cBoundary = md5.result().toHex(); +} + +MimeMultiPart::~MimeMultiPart() { + +} + +void MimeMultiPart::addPart(MimePart *part) { + parts.append(part); +} + +const QList & MimeMultiPart::getParts() const { + return parts; +} + +void MimeMultiPart::prepare() { + QList::iterator it; + + content = ""; + for (it = parts.begin(); it != parts.end(); it++) { + content += "--" + cBoundary + "\r\n"; + (*it)->prepare(); + content += (*it)->toString(); + }; + + content += "--" + cBoundary + "--\r\n"; + + MimePart::prepare(); +} + +void MimeMultiPart::setMimeType(const MultiPartType type) { + this->type = type; + this->cType = MULTI_PART_NAMES[type]; +} + +MimeMultiPart::MultiPartType MimeMultiPart::getMimeType() const { + return type; +} diff --git a/servatrice/src/smtp/mimemultipart.h b/servatrice/src/smtp/mimemultipart.h new file mode 100644 index 00000000..a8e9f599 --- /dev/null +++ b/servatrice/src/smtp/mimemultipart.h @@ -0,0 +1,75 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEMULTIPART_H +#define MIMEMULTIPART_H + +#include "mimepart.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeMultiPart : public MimePart +{ + Q_OBJECT +public: + + /* [0] Enums */ + enum MultiPartType { + Mixed = 0, // RFC 2046, section 5.1.3 + Digest = 1, // RFC 2046, section 5.1.5 + Alternative = 2, // RFC 2046, section 5.1.4 + Related = 3, // RFC 2387 + Report = 4, // RFC 6522 + Signed = 5, // RFC 1847, section 2.1 + Encrypted = 6 // RFC 1847, section 2.2 + }; + + /* [0] --- */ + + /* [1] Constructors and Destructors */ + MimeMultiPart(const MultiPartType type = Related); + + ~MimeMultiPart(); + + /* [1] --- */ + + /* [2] Getters and Setters */ + + void setMimeType(const MultiPartType type); + MultiPartType getMimeType() const; + + const QList & getParts() const; + + /* [2] --- */ + + /* [3] Public methods */ + + void addPart(MimePart *part); + + virtual void prepare(); + + /* [3] --- */ + +protected: + QList< MimePart* > parts; + + MultiPartType type; + +}; + +#endif // MIMEMULTIPART_H diff --git a/servatrice/src/smtp/mimepart.cpp b/servatrice/src/smtp/mimepart.cpp new file mode 100644 index 00000000..443f863e --- /dev/null +++ b/servatrice/src/smtp/mimepart.cpp @@ -0,0 +1,214 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimepart.h" +#include "quotedprintable.h" + +/* [1] Constructors and Destructors */ + +MimePart::MimePart() +{ + cEncoding = _7Bit; + prepared = false; + cBoundary = ""; +} + +MimePart::~MimePart() +{ + return; +} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimePart::setContent(const QByteArray & content) +{ + this->content = content; +} + +void MimePart::setHeader(const QString & header) +{ + this->header = header; +} + +void MimePart::addHeaderLine(const QString & line) +{ + this->header += line + "\r\n"; +} + +const QString& MimePart::getHeader() const +{ + return header; +} + +const QByteArray& MimePart::getContent() const +{ + return content; +} + +void MimePart::setContentId(const QString & cId) +{ + this->cId = cId; +} + +const QString & MimePart::getContentId() const +{ + return this->cId; +} + +void MimePart::setContentName(const QString & cName) +{ + this->cName = cName; +} + +const QString & MimePart::getContentName() const +{ + return this->cName; +} + +void MimePart::setContentType(const QString & cType) +{ + this->cType = cType; +} + +const QString & MimePart::getContentType() const +{ + return this->cType; +} + +void MimePart::setCharset(const QString & charset) +{ + this->cCharset = charset; +} + +const QString & MimePart::getCharset() const +{ + return this->cCharset; +} + +void MimePart::setEncoding(Encoding enc) +{ + this->cEncoding = enc; +} + +MimePart::Encoding MimePart::getEncoding() const +{ + return this->cEncoding; +} + +MimeContentFormatter& MimePart::getContentFormatter() +{ + return this->formatter; +} + +/* [2] --- */ + + +/* [3] Public methods */ + +QString MimePart::toString() +{ + if (!prepared) + prepare(); + + return mimeString; +} + +/* [3] --- */ + + +/* [4] Protected methods */ + +void MimePart::prepare() +{ + mimeString = QString(); + + /* === Header Prepare === */ + + /* Content-Type */ + mimeString.append("Content-Type: ").append(cType); + + if (cName != "") + mimeString.append("; name=\"").append(cName).append("\""); + + if (cCharset != "") + mimeString.append("; charset=").append(cCharset); + + if (cBoundary != "") + mimeString.append("; boundary=").append(cBoundary); + + mimeString.append("\r\n"); + /* ------------ */ + + /* Content-Transfer-Encoding */ + mimeString.append("Content-Transfer-Encoding: "); + switch (cEncoding) + { + case _7Bit: + mimeString.append("7bit\r\n"); + break; + case _8Bit: + mimeString.append("8bit\r\n"); + break; + case Base64: + mimeString.append("base64\r\n"); + break; + case QuotedPrintable: + mimeString.append("quoted-printable\r\n"); + break; + } + /* ------------------------ */ + + /* Content-Id */ + if (cId != NULL) + mimeString.append("Content-ID: <").append(cId).append(">\r\n"); + /* ---------- */ + + /* Addition header lines */ + + mimeString.append(header).append("\r\n"); + + /* ------------------------- */ + + /* === End of Header Prepare === */ + + /* === Content === */ + switch (cEncoding) + { + case _7Bit: + mimeString.append(QString(content).toLatin1()); + break; + case _8Bit: + mimeString.append(content); + break; + case Base64: + mimeString.append(formatter.format(content.toBase64())); + break; + case QuotedPrintable: + mimeString.append(formatter.format(QuotedPrintable::encode(content), true)); + break; + } + mimeString.append("\r\n"); + /* === End of Content === */ + + prepared = true; +} + +/* [4] --- */ diff --git a/servatrice/src/smtp/mimepart.h b/servatrice/src/smtp/mimepart.h new file mode 100644 index 00000000..8636035d --- /dev/null +++ b/servatrice/src/smtp/mimepart.h @@ -0,0 +1,114 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMEPART_H +#define MIMEPART_H + +#include +#include "mimecontentformatter.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimePart : public QObject +{ + Q_OBJECT +public: + + /* [0] Enumerations */ + enum Encoding { + _7Bit, + _8Bit, + Base64, + QuotedPrintable + }; + + + /* [0] --- */ + + + /* [1] Constructors and Destructors */ + + MimePart(); + ~MimePart(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + const QString& getHeader() const; + const QByteArray& getContent() const; + + void setContent(const QByteArray & content); + void setHeader(const QString & header); + + void addHeaderLine(const QString & line); + + void setContentId(const QString & cId); + const QString & getContentId() const; + + void setContentName(const QString & cName); + const QString & getContentName() const; + + void setContentType(const QString & cType); + const QString & getContentType() const; + + void setCharset(const QString & charset); + const QString & getCharset() const; + + void setEncoding(Encoding enc); + Encoding getEncoding() const; + + MimeContentFormatter& getContentFormatter(); + + /* [2] --- */ + + + /* [3] Public methods */ + + virtual QString toString(); + + virtual void prepare(); + + /* [3] --- */ + + + +protected: + + /* [4] Protected members */ + + QString header; + QByteArray content; + + QString cId; + QString cName; + QString cType; + QString cCharset; + QString cBoundary; + Encoding cEncoding; + + QString mimeString; + bool prepared; + + MimeContentFormatter formatter; + + /* [4] --- */ +}; + +#endif // MIMEPART_H diff --git a/servatrice/src/smtp/mimetext.cpp b/servatrice/src/smtp/mimetext.cpp new file mode 100644 index 00000000..20b1b0a0 --- /dev/null +++ b/servatrice/src/smtp/mimetext.cpp @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "mimetext.h" + +/* [1] Constructors and Destructors */ + +MimeText::MimeText(const QString &txt) +{ + this->text = txt; + this->cType = "text/plain"; + this->cCharset = "utf-8"; + this->cEncoding = _8Bit; +} + +MimeText::~MimeText() { } + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void MimeText::setText(const QString & text) +{ + this->text = text; +} + +const QString & MimeText::getText() const +{ + return text; +} + +/* [2] --- */ + + +/* [3] Protected Methods */ + +void MimeText::prepare() +{ + this->content.clear(); + this->content.append(text); + + /* !!! IMPORTANT !!! */ + MimePart::prepare(); +} + +/* [3] --- */ diff --git a/servatrice/src/smtp/mimetext.h b/servatrice/src/smtp/mimetext.h new file mode 100644 index 00000000..c0d29835 --- /dev/null +++ b/servatrice/src/smtp/mimetext.h @@ -0,0 +1,62 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef MIMETEXT_H +#define MIMETEXT_H + +#include "mimepart.h" + +#include "smtpexports.h" + +class SMTP_EXPORT MimeText : public MimePart +{ +public: + + /* [1] Constructors and Destructors */ + + MimeText(const QString &text = ""); + ~MimeText(); + + /* [1] --- */ + + + /* [2] Getters and Setters*/ + + void setText(const QString & text); + + const QString & getText() const; + + /* [2] --- */ + +protected: + + /* [3] Protected members */ + + QString text; + /* [3] --- */ + + + /* [4] Protected methods */ + + void prepare(); + + /* [4] --- */ + +}; + +#endif // MIMETEXT_H diff --git a/servatrice/src/smtp/quotedprintable.cpp b/servatrice/src/smtp/quotedprintable.cpp new file mode 100644 index 00000000..aa3eda6a --- /dev/null +++ b/servatrice/src/smtp/quotedprintable.cpp @@ -0,0 +1,69 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "quotedprintable.h" + +QString QuotedPrintable::encode(const QByteArray &input) +{ + QString output; + + char byte; + const char hex[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + for (int i = 0; i < input.length() ; ++i) + { + byte = input[i]; + + if ((byte == 0x20) || ((byte >= 33) && (byte <= 126) && (byte != 61))) + { + output.append(byte); + } + else + { + output.append('='); + output.append(hex[((byte >> 4) & 0x0F)]); + output.append(hex[(byte & 0x0F)]); + } + } + + return output; +} + + +QByteArray QuotedPrintable::decode(const QString &input) +{ + // 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F + const int hexVal[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15}; + + QByteArray output; + + for (int i = 0; i < input.length(); ++i) + { + if (input.at(i).toLatin1() == '=') + { + output.append((hexVal[input.at(i + 1).toLatin1() - '0'] << 4) + hexVal[input.at(i + 2).toLatin1() - '0']); + i += 2; + } + else + { + output.append(input.at(i).toLatin1()); + } + } + + return output; +} diff --git a/servatrice/src/smtp/quotedprintable.h b/servatrice/src/smtp/quotedprintable.h new file mode 100644 index 00000000..03d3acf8 --- /dev/null +++ b/servatrice/src/smtp/quotedprintable.h @@ -0,0 +1,39 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef QUOTEDPRINTABLE_H +#define QUOTEDPRINTABLE_H + +#include +#include + +#include "smtpexports.h" + +class SMTP_EXPORT QuotedPrintable : public QObject +{ + Q_OBJECT +public: + + static QString encode(const QByteArray &input); + static QByteArray decode(const QString &input); + +private: + QuotedPrintable(); +}; + +#endif // QUOTEDPRINTABLE_H diff --git a/servatrice/src/smtp/smtpclient.cpp b/servatrice/src/smtp/smtpclient.cpp new file mode 100644 index 00000000..37988871 --- /dev/null +++ b/servatrice/src/smtp/smtpclient.cpp @@ -0,0 +1,482 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#include "smtpclient.h" + +#include +#include + + +/* [1] Constructors and destructors */ + +SmtpClient::SmtpClient(const QString & host, int port, ConnectionType connectionType) : + name("localhost"), + authMethod(AuthPlain), + connectionTimeout(5000), + responseTimeout(5000), + sendMessageTimeout(60000) +{ + setConnectionType(connectionType); + + this->host = host; + this->port = port; + + connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(socketStateChanged(QAbstractSocket::SocketState))); + connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(socketError(QAbstractSocket::SocketError))); + connect(socket, SIGNAL(readyRead()), + this, SLOT(socketReadyRead())); +} + +SmtpClient::~SmtpClient() {} + +/* [1] --- */ + + +/* [2] Getters and Setters */ + +void SmtpClient::setUser(const QString &user) +{ + this->user = user; +} + +void SmtpClient::setPassword(const QString &password) +{ + this->password = password; +} + +void SmtpClient::setAuthMethod(AuthMethod method) +{ + this->authMethod = method; +} + +void SmtpClient::setHost(const QString &host) +{ + this->host = host; +} + +void SmtpClient::setPort(int port) +{ + this->port = port; +} + +void SmtpClient::setConnectionType(ConnectionType ct) +{ + this->connectionType = ct; + + switch (connectionType) + { + case TcpConnection: + socket = new QTcpSocket(this); + break; + case SslConnection: + case TlsConnection: + socket = new QSslSocket(this); + } +} + +const QString& SmtpClient::getHost() const +{ + return this->host; +} + +const QString& SmtpClient::getUser() const +{ + return this->user; +} + +const QString& SmtpClient::getPassword() const +{ + return this->password; +} + +SmtpClient::AuthMethod SmtpClient::getAuthMethod() const +{ + return this->authMethod; +} + +int SmtpClient::getPort() const +{ + return this->port; +} + +SmtpClient::ConnectionType SmtpClient::getConnectionType() const +{ + return connectionType; +} + +const QString& SmtpClient::getName() const +{ + return this->name; +} + +void SmtpClient::setName(const QString &name) +{ + this->name = name; +} + +const QString & SmtpClient::getResponseText() const +{ + return responseText; +} + +int SmtpClient::getResponseCode() const +{ + return responseCode; +} + +QTcpSocket* SmtpClient::getSocket() { + return socket; +} + +int SmtpClient::getConnectionTimeout() const +{ + return connectionTimeout; +} + +void SmtpClient::setConnectionTimeout(int msec) +{ + connectionTimeout = msec; +} + +int SmtpClient::getResponseTimeout() const +{ + return responseTimeout; +} + +void SmtpClient::setResponseTimeout(int msec) +{ + responseTimeout = msec; +} +int SmtpClient::getSendMessageTimeout() const +{ + return sendMessageTimeout; +} +void SmtpClient::setSendMessageTimeout(int msec) +{ + sendMessageTimeout = msec; +} + +/* [2] --- */ + + +/* [3] Public methods */ + +bool SmtpClient::connectToHost() +{ + switch (connectionType) + { + case TlsConnection: + case TcpConnection: + socket->connectToHost(host, port); + break; + case SslConnection: + ((QSslSocket*) socket)->connectToHostEncrypted(host, port); + break; + + } + + // Tries to connect to server + if (!socket->waitForConnected(connectionTimeout)) + { + emit smtpError(ConnectionTimeoutError); + return false; + } + + try + { + // Wait for the server's response + waitForResponse(); + + // If the response code is not 220 (Service ready) + // means that is something wrong with the server + if (responseCode != 220) + { + emit smtpError(ServerError); + return false; + } + + // Send a EHLO/HELO message to the server + // The client's first command must be EHLO/HELO + sendMessage("EHLO " + name); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 250. + if (responseCode != 250) { + emit smtpError(ServerError); + return false; + } + + if (connectionType == TlsConnection) { + // send a request to start TLS handshake + sendMessage("STARTTLS"); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 220. + if (responseCode != 220) { + emit smtpError(ServerError); + return false; + }; + + ((QSslSocket*) socket)->startClientEncryption(); + + if (!((QSslSocket*) socket)->waitForEncrypted(connectionTimeout)) { + qDebug() << ((QSslSocket*) socket)->errorString(); + emit smtpError(ConnectionTimeoutError); + return false; + } + + // Send ELHO one more time + sendMessage("EHLO " + name); + + // Wait for the server's response + waitForResponse(); + + // The response code needs to be 250. + if (responseCode != 250) { + emit smtpError(ServerError); + return false; + } + } + } + catch (ResponseTimeoutException) + { + return false; + } + catch (SendMessageTimeoutException) + { + return false; + } + + // If no errors occured the function returns true. + return true; +} + +bool SmtpClient::login() +{ + return login(user, password, authMethod); +} + +bool SmtpClient::login(const QString &user, const QString &password, AuthMethod method) +{ + try { + if (method == AuthPlain) + { + // Sending command: AUTH PLAIN base64('\0' + username + '\0' + password) + sendMessage("AUTH PLAIN " + QByteArray().append((char) 0).append(user).append((char) 0).append(password).toBase64()); + + // Wait for the server's response + waitForResponse(); + + // If the response is not 235 then the authentication was faild + if (responseCode != 235) + { + emit smtpError(AuthenticationFailedError); + return false; + } + } + else if (method == AuthLogin) + { + // Sending command: AUTH LOGIN + sendMessage("AUTH LOGIN"); + + // Wait for 334 response code + waitForResponse(); + if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; } + + // Send the username in base64 + sendMessage(QByteArray().append(user).toBase64()); + + // Wait for 334 + waitForResponse(); + if (responseCode != 334) { emit smtpError(AuthenticationFailedError); return false; } + + // Send the password in base64 + sendMessage(QByteArray().append(password).toBase64()); + + // Wait for the server's responce + waitForResponse(); + + // If the response is not 235 then the authentication was faild + if (responseCode != 235) + { + emit smtpError(AuthenticationFailedError); + return false; + } + } + } + catch (ResponseTimeoutException e) + { + // Responce Timeout exceeded + emit smtpError(AuthenticationFailedError); + return false; + } + catch (SendMessageTimeoutException) + { + // Send Timeout exceeded + emit smtpError(AuthenticationFailedError); + return false; + } + + return true; +} + +bool SmtpClient::sendMail(MimeMessage& email) +{ + try + { + // Send the MAIL command with the sender + sendMessage("MAIL FROM: <" + email.getSender().getAddress() + ">"); + + waitForResponse(); + + if (responseCode != 250) return false; + + // Send RCPT command for each recipient + QList::const_iterator it, itEnd; + // To (primary recipients) + for (it = email.getRecipients().begin(), itEnd = email.getRecipients().end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Cc (carbon copy) + for (it = email.getRecipients(MimeMessage::Cc).begin(), itEnd = email.getRecipients(MimeMessage::Cc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Bcc (blind carbon copy) + for (it = email.getRecipients(MimeMessage::Bcc).begin(), itEnd = email.getRecipients(MimeMessage::Bcc).end(); + it != itEnd; ++it) + { + sendMessage("RCPT TO: <" + (*it)->getAddress() + ">"); + waitForResponse(); + + if (responseCode != 250) return false; + } + + // Send DATA command + sendMessage("DATA"); + waitForResponse(); + + if (responseCode != 354) return false; + + sendMessage(email.toString()); + + // Send \r\n.\r\n to end the mail data + sendMessage("."); + + waitForResponse(); + + if (responseCode != 250) return false; + } + catch (ResponseTimeoutException) + { + return false; + } + catch (SendMessageTimeoutException) + { + return false; + } + + return true; +} + +void SmtpClient::quit() +{ + sendMessage("QUIT"); +} + +/* [3] --- */ + + +/* [4] Protected methods */ + +void SmtpClient::waitForResponse() throw (ResponseTimeoutException) +{ + do { + if (!socket->waitForReadyRead(responseTimeout)) + { + emit smtpError(ResponseTimeoutError); + throw ResponseTimeoutException(); + } + + while (socket->canReadLine()) { + // Save the server's response + responseText = socket->readLine(); + + // Extract the respose code from the server's responce (first 3 digits) + responseCode = responseText.left(3).toInt(); + + if (responseCode / 100 == 4) + emit smtpError(ServerError); + + if (responseCode / 100 == 5) + emit smtpError(ClientError); + + if (responseText[3] == ' ') { return; } + } + } while (true); +} + +void SmtpClient::sendMessage(const QString &text) throw (SendMessageTimeoutException) +{ + socket->write(text.toUtf8() + "\r\n"); + if (! socket->waitForBytesWritten(sendMessageTimeout)) + { + emit smtpError(SendDataTimeoutError); + throw SendMessageTimeoutException(); + } +} + +/* [4] --- */ + + +/* [5] Slots for the socket's signals */ + +void SmtpClient::socketStateChanged(QAbstractSocket::SocketState /* state */) +{ +} + +void SmtpClient::socketError(QAbstractSocket::SocketError /* socketError */) +{ +} + +void SmtpClient::socketReadyRead() +{ +} + +/* [5] --- */ + + + + diff --git a/servatrice/src/smtp/smtpclient.h b/servatrice/src/smtp/smtpclient.h new file mode 100644 index 00000000..f734aad6 --- /dev/null +++ b/servatrice/src/smtp/smtpclient.h @@ -0,0 +1,184 @@ +/* + Copyright (c) 2011-2012 - Tőkés Attila + + This file is part of SmtpClient for Qt. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + See the LICENSE file for more details. +*/ + +#ifndef SMTPCLIENT_H +#define SMTPCLIENT_H + +#include +#include + +#include "mimemessage.h" +#include "smtpexports.h" + +class SMTP_EXPORT SmtpClient : public QObject +{ + Q_OBJECT +public: + + /* [0] Enumerations */ + + enum AuthMethod + { + AuthPlain, + AuthLogin + }; + + enum SmtpError + { + ConnectionTimeoutError, + ResponseTimeoutError, + SendDataTimeoutError, + AuthenticationFailedError, + ServerError, // 4xx smtp error + ClientError // 5xx smtp error + }; + + enum ConnectionType + { + TcpConnection, + SslConnection, + TlsConnection // STARTTLS + }; + + /* [0] --- */ + + + /* [1] Constructors and Destructors */ + + SmtpClient(const QString & host = "localhost", int port = 25, ConnectionType ct = TcpConnection); + + ~SmtpClient(); + + /* [1] --- */ + + + /* [2] Getters and Setters */ + + const QString& getHost() const; + void setHost(const QString &host); + + int getPort() const; + void setPort(int port); + + const QString& getName() const; + void setName(const QString &name); + + ConnectionType getConnectionType() const; + void setConnectionType(ConnectionType ct); + + const QString & getUser() const; + void setUser(const QString &user); + + const QString & getPassword() const; + void setPassword(const QString &password); + + SmtpClient::AuthMethod getAuthMethod() const; + void setAuthMethod(AuthMethod method); + + const QString & getResponseText() const; + int getResponseCode() const; + + int getConnectionTimeout() const; + void setConnectionTimeout(int msec); + + int getResponseTimeout() const; + void setResponseTimeout(int msec); + + int getSendMessageTimeout() const; + void setSendMessageTimeout(int msec); + + QTcpSocket* getSocket(); + + + /* [2] --- */ + + + /* [3] Public methods */ + + bool connectToHost(); + + bool login(); + bool login(const QString &user, const QString &password, AuthMethod method = AuthLogin); + + bool sendMail(MimeMessage& email); + + void quit(); + + + /* [3] --- */ + +protected: + + /* [4] Protected members */ + + QTcpSocket *socket; + + QString host; + int port; + ConnectionType connectionType; + QString name; + + QString user; + QString password; + AuthMethod authMethod; + + int connectionTimeout; + int responseTimeout; + int sendMessageTimeout; + + + QString responseText; + int responseCode; + + + class ResponseTimeoutException {}; + class SendMessageTimeoutException {}; + + /* [4] --- */ + + + /* [5] Protected methods */ + + void waitForResponse() throw (ResponseTimeoutException); + + void sendMessage(const QString &text) throw (SendMessageTimeoutException); + + /* [5] --- */ + +protected slots: + + /* [6] Protected slots */ + + void socketStateChanged(QAbstractSocket::SocketState state); + void socketError(QAbstractSocket::SocketError error); + void socketReadyRead(); + + /* [6] --- */ + + +signals: + + /* [7] Signals */ + + void smtpError(SmtpClient::SmtpError e); + + /* [7] --- */ + +}; + +#endif // SMTPCLIENT_H diff --git a/servatrice/src/smtp/smtpexports.h b/servatrice/src/smtp/smtpexports.h new file mode 100644 index 00000000..c9234be5 --- /dev/null +++ b/servatrice/src/smtp/smtpexports.h @@ -0,0 +1,15 @@ +#ifndef SMTPEXPORTS_H +#define SMTPEXPORTS_H + +/* +#ifdef SMTP_BUILD +#define SMTP_EXPORT Q_DECL_EXPORT +#else +#define SMTP_EXPORT Q_DECL_IMPORT +#endif +*/ + +// Servatrice compiles this statically in, so there's no need to declare exports +#define SMTP_EXPORT + +#endif // SMTPEXPORTS_H