diff options
Diffstat (limited to 'pica-client')
38 files changed, 2875 insertions, 0 deletions
diff --git a/pica-client/accounts.cpp b/pica-client/accounts.cpp new file mode 100644 index 0000000..e9cf0e9 --- /dev/null +++ b/pica-client/accounts.cpp @@ -0,0 +1,107 @@ +#include "accounts.h" +#include <QFile> + +Accounts::Accounts(QString storage) +{ + dbconn=QSqlDatabase::addDatabase("QSQLITE"); + dbconn.setDatabaseName(storage); + + if (!dbconn.open()) + { + lasterr=dbconn.lastError(); + return; + } + + QSqlQuery query; + + query.exec("PRAGMA foreign_keys=ON;"); + + //check result, if foreign keys support is on +} + +void Accounts::Add(AccountRecord &acc) +{ + QSqlQuery query; + + query.prepare("insert into accounts (id, name, cert_file, pkey_file, ca_file) values (:id, :name, :cert_file, :pkey_file, :ca_file);"); + query.bindValue(":id",acc.id); + query.bindValue(":name",acc.name); + query.bindValue(":cert_file",acc.cert_file); + query.bindValue(":pkey_file",acc.pkey_file); + query.bindValue(":ca_file",acc.CA_file); + query.exec(); + + lasterr=query.lastError(); +} + +void Accounts::Delete(quint32 id) +{ + QSqlQuery query; + + query.prepare("delete from accounts where id=:id"); + query.bindValue(":id",id); + query.exec(); + + lasterr=query.lastError(); +} + +QList<Accounts::AccountRecord> Accounts::GetAccounts() +{ + AccountRecord r; + QList<Accounts::AccountRecord> L; + QSqlQuery query; + + // + query.setForwardOnly(true); + query.exec("select id, name, cert_file, pkey_file, ca_file from accounts"); + lasterr=query.lastError(); + + if (lasterr.isValid()) + return L; + + while(query.next()) + { + r.id = query.value(0).toUInt(); + r.name = query.value(1).toString(); + r.cert_file = query.value(2).toString(); + r.pkey_file = query.value(3).toString(); + r.CA_file = query.value(4).toString(); + + L<<r; + } + + return L; +} + +bool Accounts::isOK() +{ + return !lasterr.isValid(); +} + +QString Accounts::GetLastError() +{ + return lasterr.text(); +} + +bool Accounts::CheckFiles(AccountRecord &acc, QString &error_string) +{ + if (!QFile::exists(acc.cert_file)) + { + error_string = QObject::tr("Certificate file %1 does not exist").arg(acc.cert_file); + return false; + } + + if (!QFile::exists(acc.pkey_file)) + { + error_string = QObject::tr("Private key file %1 does not exist").arg(acc.pkey_file); + return false; + } + + if (!QFile::exists(acc.CA_file)) + { + error_string = QObject::tr("Certificate Authority file %1 does not exist").arg(acc.CA_file); + return false; + } + + return true; +} diff --git a/pica-client/accounts.h b/pica-client/accounts.h new file mode 100644 index 0000000..1205583 --- /dev/null +++ b/pica-client/accounts.h @@ -0,0 +1,35 @@ +#ifndef ACCOUNTS_H +#define ACCOUNTS_H + +#include <QString> +#include <QtSql> +#include <QSqlDatabase> +#include <QStringList> + +class Accounts +{ +public: + struct AccountRecord + { + quint32 id; + QString name; + QString cert_file; + QString pkey_file; + QString CA_file; + }; + + Accounts(QString storage); + void Add(AccountRecord& acc); + void Delete(quint32 id); + QList<AccountRecord> GetAccounts(); + bool isOK(); + QString GetLastError(); + + static bool CheckFiles(AccountRecord& acc, QString &error_string); + +private: + QSqlDatabase dbconn; + QSqlError lasterr; +}; + +#endif // ACCOUNTS_H diff --git a/pica-client/accountswindow.cpp b/pica-client/accountswindow.cpp new file mode 100644 index 0000000..151e366 --- /dev/null +++ b/pica-client/accountswindow.cpp @@ -0,0 +1,205 @@ +#include "accountswindow.h" +#include <QVBoxLayout> +#include "mainwindow.h" +#include "globals.h" +#include "dialogs/addaccountdialog.h" +#include "../PICA_client.h" +#include "skynet.h" +#include <QRegExp> +#include <QMessageBox> +#include <QApplication> + +AccountsWindow::AccountsWindow(QWidget *parent) : + QMainWindow(parent), + accounts(config_dbname) +{ + QVBoxLayout *layout; + layout = new QVBoxLayout; + + cb_accounts = new QComboBox(); + bt_login = new QPushButton(tr("Log In")); + bt_registernew = new QPushButton(tr("Register New Account...")); + bt_addimport = new QPushButton(tr("Add (Import) existing account...")); + bt_delete = new QPushButton(tr("Delete Account")); + + { + QFont f; + f= bt_login->font(); + f.setBold(true); + bt_login->setFont(f); + } + + layout->addWidget(cb_accounts); + layout->addWidget(bt_login); + layout->addWidget(bt_registernew); + layout->addWidget(bt_addimport); + layout->addWidget(bt_delete); + + QWidget *central = new QWidget(this); + setCentralWidget(central); + + centralWidget()->setLayout(layout); + + setWindowIcon(picapica_ico_sit); + + connect(bt_login,SIGNAL(clicked()),this,SLOT(login_click())); + connect(bt_registernew,SIGNAL(clicked()),this,SLOT(register_click())); + connect(bt_addimport,SIGNAL(clicked()),this,SLOT(add_click())); + connect(bt_delete,SIGNAL(clicked()),this,SLOT(delete_click())); + + LoadAccounts(); +} + +void AccountsWindow::LoadAccounts() +{ + cb_accounts->clear(); + L.clear(); + L=accounts.GetAccounts(); + + if (0==L.count()) + { + cb_accounts->addItem(tr(" ---No Accounts --")); + + bt_login->setDisabled(true); + } + else + { + for (int i=0;i<L.count();i++) + cb_accounts->addItem(QString("(%1) %2").arg(L[i].id).arg(L[i].name),L[i].id); + + bt_login->setEnabled(true); + } +} + +void AccountsWindow::login_click() +{ + + account_id = (cb_accounts->itemData(cb_accounts->currentIndex())).toUInt(); + + int L_index; + + for (int i=0;i<L.count();i++) + { + if (L[i].id==account_id) + { + L_index = i; + break; + } + } + + { + QString error_str; + if (!Accounts::CheckFiles(L[L_index],error_str)) + { + QMessageBox mbx; + mbx.setText(error_str); + mbx.exec(); + return; + } + } + skynet->Join(L[L_index]); + + mainwindow = new MainWindow(); + mainwindow->show(); + this->hide(); + +} + +void AccountsWindow::register_click() +{ + +} + +void AccountsWindow::add_click() +{ + AddAccountDialog d; + d.exec(); + + if (d.result()) + { + quint32 id; + + PICA_get_id_from_cert(d.GetCertFilename().toUtf8().constData(),&id); + + // + QProcess openssl; + + openssl.start("openssl",QStringList()<<"x509"<<"-in"<<d.GetCertFilename()<<"-subject"<<"-nameopt"<<"oneline,-esc_msb,utf8"<<"-noout"); + if (!openssl.waitForStarted()) + return; + + if (!openssl.waitForFinished()) + return; + + QByteArray result = openssl.readAll(); + QRegExp rx("CN = [0-9]+\\#([^\\s\\#]+)"); + + + QString name=QString::fromUtf8( result.constData()); + + if (name.contains(rx)) + { + name=rx.cap(1); + } + + // + Accounts::AccountRecord rec; + QString certfilename,pkeyfilename; + + if (d.isCopyFilesChecked()) + { + QDir dir(config_dir); + dir.mkdir(QString::number(id)); + dir.cd(QString::number(id)); + + certfilename=dir.absolutePath()+"/"+QString::number(id)+"_cert.pem"; + pkeyfilename=dir.absolutePath()+"/"+QString::number(id)+"_pkey.pem"; + + QFile::copy(d.GetCertFilename(),certfilename); + QFile::copy(d.GetPkeyFilename(),pkeyfilename); + } + else + { + certfilename = d.GetCertFilename(); + pkeyfilename = d.GetPkeyFilename(); + } + + rec.id=id; + rec.name=name; + rec.cert_file=certfilename; + rec.pkey_file=pkeyfilename; + rec.CA_file= config_defaultCA; + + accounts.Add(rec); + + if (!accounts.isOK()) + { + QMessageBox mbx; + mbx.setText(accounts.GetLastError()); + mbx.exec(); + } + LoadAccounts(); + } +} + +void AccountsWindow::delete_click() +{ + quint32 id = (cb_accounts->itemData(cb_accounts->currentIndex())).toUInt(); + + if (QMessageBox::No== + QMessageBox::question(this,tr("Deleting account"),tr("Are you sure you want to delete account %1 ?").arg(id),QMessageBox::Yes,QMessageBox::No)) + return; + + //TODO ask user if he wants to wipe certificate and private key too + + accounts.Delete(id); + + LoadAccounts(); + +} + +void AccountsWindow::closeEvent(QCloseEvent *e) +{ + QApplication::instance()->quit(); + QMainWindow::closeEvent(e); +} diff --git a/pica-client/accountswindow.h b/pica-client/accountswindow.h new file mode 100644 index 0000000..30e3c0d --- /dev/null +++ b/pica-client/accountswindow.h @@ -0,0 +1,43 @@ +#ifndef ACCOUNTSWINDOW_H +#define ACCOUNTSWINDOW_H + +#include <QMainWindow> +#include <QComboBox> +#include <QPushButton> +#include "accounts.h" +#include <QList> + +class AccountsWindow : public QMainWindow +{ + Q_OBJECT +public: + explicit AccountsWindow(QWidget *parent = 0); + +private: + QComboBox *cb_accounts; + QPushButton *bt_login; + QPushButton *bt_registernew; + QPushButton *bt_addimport; + QPushButton *bt_delete; + + Accounts accounts; + QList<Accounts::AccountRecord> L; + + void LoadAccounts(); + + void closeEvent(QCloseEvent *); + + +signals: + +public slots: + +private slots: + void login_click(); + void register_click(); + void add_click(); + void delete_click(); + +}; + +#endif // ACCOUNTSWINDOW_H diff --git a/pica-client/askpassword.cpp b/pica-client/askpassword.cpp new file mode 100644 index 0000000..0629502 --- /dev/null +++ b/pica-client/askpassword.cpp @@ -0,0 +1,52 @@ +#include "askpassword.h" +#include "globals.h" +#include <string.h> +#include <QInputDialog> + +QMutex AskPassword::passwd_mutex; +QMap<quint32,QString> AskPassword::idpasswd; + +AskPassword::AskPassword(QObject *parent) : + QObject(parent) +{ + connect(this,SIGNAL(password_requested()),this,SLOT(password_dialog()),Qt::BlockingQueuedConnection); +} + +int AskPassword::ask_password_cb(char *buf, int size, int rwflag, void *userdata) +{ + QString pswd; + quint32 id = *((quint32*)userdata); + + passwd_mutex.lock(); + + if (idpasswd.contains(id)) + { + pswd = idpasswd[id]; + } + else + { + askpassword->emit_sig(); + pswd = idpasswd[id]; + } + passwd_mutex.unlock(); + + strncpy(buf, pswd.toUtf8().constData(), size); + buf[size - 1] = 0; + return(strlen(buf)); +} + +void AskPassword::emit_sig() +{ + emit password_requested(); +} + +void AskPassword::password_dialog() +{ + bool ok; + QString title = QObject::tr("Enter private key passphrase"); + QString text = QObject::tr(QString("Passphrase for (%1)").arg(account_id).toUtf8().constData()); + QString psw = QInputDialog::getText((QWidget*)mainwindow,title,text,QLineEdit::Password,QString(),&ok); + if (ok) + idpasswd[account_id] = psw; +} + diff --git a/pica-client/askpassword.h b/pica-client/askpassword.h new file mode 100644 index 0000000..c841693 --- /dev/null +++ b/pica-client/askpassword.h @@ -0,0 +1,27 @@ +#ifndef ASKPASSWORD_H +#define ASKPASSWORD_H + +#include <QObject> +#include <QMap> +#include <QMutex> + +class AskPassword : public QObject +{ + Q_OBJECT +public: + explicit AskPassword(QObject *parent = 0); + static int ask_password_cb(char *buf, int size, int rwflag, void *userdata); + +private: + void emit_sig(); + static QMap<quint32,QString> idpasswd; + static QMutex passwd_mutex; + +signals: + void password_requested(); + +public slots: + void password_dialog(); +}; + +#endif // ASKPASSWORD_H diff --git a/pica-client/chatwindow.cpp b/pica-client/chatwindow.cpp new file mode 100644 index 0000000..dd495ae --- /dev/null +++ b/pica-client/chatwindow.cpp @@ -0,0 +1,140 @@ +#include "chatwindow.h" +#include "globals.h" +#include <QVBoxLayout> +#include <QDateTime> +#include <QAction> +#include <QKeyEvent> +#include <QMessageBox> + +ChatWindow::ChatWindow(quint32 peer_id) : + QWidget(0), peer_id_(peer_id) +{ + QVBoxLayout *layout; + + layout = new QVBoxLayout; + + chatw = new QTextEdit(this); + sendtextw = new TextSend(this); + + connect(sendtextw, SIGNAL(send_pressed()), this, SLOT(send_message())); + + sendtextw->setMinimumHeight(48); + sendtextw->setMinimumWidth(240); + sendtextw->setAcceptRichText(false); + + chatw->setReadOnly(true); + + layout->addWidget(chatw, 3); + layout->addWidget(sendtextw, 1, Qt::AlignBottom); + + setLayout(layout); + + setWindowTitle(QString::number(peer_id)); + setWindowIcon(picapica_ico_sit); + + QWidget::setAttribute(Qt::WA_DeleteOnClose); + QWidget::setAttribute(Qt::WA_QuitOnClose, false); +} + +void ChatWindow::put_message(QString msg, quint32 id, bool is_me) +{ + QString color; + QTextCharFormat fmt; + + if (is_me) + color = "#0000ff"; + else + color = "#ff0000"; + + QTextCursor c(chatw->document()); + + c.movePosition(QTextCursor::End, QTextCursor::MoveAnchor); + + if (is_me) + undelivered_msgs.append(c.position()); + + fmt = chatw->currentCharFormat(); + chatw->insertHtml( + QString("<b><font color=%1> (%2) %3 : </font></b>"). + arg(color). + arg(QDateTime::currentDateTime().toString()). + arg(QString::number(id)) + ); + chatw->setCurrentCharFormat(fmt); + chatw->insertPlainText(msg + '\n'); + + chatw->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor); +} + +void ChatWindow::msg_from_peer(QString msg) +{ + if (isMinimized()) + showNormal(); + + put_message(msg, peer_id_, false); +} + +void ChatWindow::msg_delivered() +{ + QTextCursor c(chatw->document()); + c.setPosition(undelivered_msgs.first()); + c.deleteChar(); + c.insertText("+"); + undelivered_msgs.removeFirst(); +} + +void ChatWindow::msg_informational(QString text) +{ + QString color; + QTextCharFormat fmt; + + color = "#303030"; + + fmt = chatw->currentCharFormat(); + + chatw->insertHtml( + QString("<i><font color=%1>(%2)</font></i>"). + arg(color). + arg(text) + ); + chatw->insertPlainText("\n"); + + chatw->setCurrentCharFormat(fmt); + chatw->moveCursor(QTextCursor::End, QTextCursor::KeepAnchor); +} + +void ChatWindow::send_message() +{ + put_message(sendtextw->toPlainText(), account_id, true); + + emit msg_input(sendtextw->toPlainText(), this); + + sendtextw->clear(); +} + +void ChatWindow::closeEvent(QCloseEvent *e) +{ + emit chatwindow_close(this); + + e->accept(); +} + +TextSend::TextSend(QWidget *parent) : + QTextEdit(parent) +{ + +} + +void TextSend::keyPressEvent(QKeyEvent *e) +{ + if (e->key() == Qt::Key_Return && e->modifiers() == Qt::NoModifier ) + { + emit send_pressed(); + return; + } + + QTextEdit::keyPressEvent(e); +} + + + diff --git a/pica-client/chatwindow.h b/pica-client/chatwindow.h new file mode 100644 index 0000000..1babd31 --- /dev/null +++ b/pica-client/chatwindow.h @@ -0,0 +1,53 @@ +#ifndef CHATWINDOW_H +#define CHATWINDOW_H + +#include <QWidget> +#include <QTextEdit> +#include <QList> + +class TextSend : public QTextEdit +{ + Q_OBJECT +public: + explicit TextSend(QWidget *parent); +signals: + void send_pressed(); +private: + void keyPressEvent(QKeyEvent *e); + +}; + +class ChatWindow : public QWidget +{ + Q_OBJECT +public: + explicit ChatWindow(quint32 peer_id); + + quint32 getPeerId() { return peer_id_;}; + +signals: + void msg_input(QString msg, ChatWindow *sender_window); + void chatwindow_close(ChatWindow *sender_window); + +public slots: + void msg_from_peer(QString msg); + void msg_delivered(); + void msg_informational(QString text);//юзер в оффлайне, набирает сообщение, и т.д. серым шрифтом, курсив + + +private: + QTextEdit *chatw; + TextSend *sendtextw; + quint32 peer_id_; + QList<int> undelivered_msgs; + + + void put_message(QString msg, quint32 id, bool is_me); + + void closeEvent(QCloseEvent *e); + +private slots: + void send_message(); +}; + +#endif // CHATWINDOW_H diff --git a/pica-client/contactlistwidget.cpp b/pica-client/contactlistwidget.cpp new file mode 100644 index 0000000..bff36bd --- /dev/null +++ b/pica-client/contactlistwidget.cpp @@ -0,0 +1,188 @@ +#include "contactlistwidget.h" +#include "globals.h" +#include "dialogs/viewcertdialog.h" +#include "msguirouter.h" +#include <QMenu> +#include <QMessageBox> +#include <QContextMenuEvent> +#include <QInputDialog> + +ContactListWidget::ContactListWidget(QWidget *parent) : + QListWidget(parent) +{ + + addcontactAct = new QAction(tr("&Add Contact..."), this); + addcontactAct->setStatusTip(tr("add new contact")); + connect(addcontactAct,SIGNAL(triggered()),this,SLOT(add_contact())); + + + delcontactAct = new QAction(tr("&Delete Contact"),this); + connect(delcontactAct,SIGNAL(triggered()),this,SLOT(del_contact())); + + startchatAct = new QAction(tr("Start &Chat"), this); + connect(startchatAct, SIGNAL(triggered()), this, SLOT(start_chat())); + + viewcertAct = new QAction(tr("&View Certificate"), this); + connect(viewcertAct, SIGNAL(triggered()), this, SLOT(view_cert())); + + connect(this, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(start_chat())); + + setContactsStorage(new Contacts(config_dbname, account_id)); +} + +ContactListWidget::~ContactListWidget() +{ + delete storage; +} + + +void ContactListWidget::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + + if (this->itemAt(event->pos())) + menu.addAction(delcontactAct); + + menu.addAction(addcontactAct); + menu.addAction(startchatAct); + menu.addAction(viewcertAct); + menu.exec(event->globalPos()); +} + +void ContactListWidget::setContactsStorage(Contacts *ct) +{ + clear(); + // this->addItems(ct->GetContacts()); + storage=ct; + contact_records=ct->GetContacts(); + + for (int i=0;i<contact_records.size();i++) + { + addItem(QString("(%1) ").arg(contact_records[i].id)+contact_records[i].name); + wgitem_to_recs[this->item(i)]=&contact_records[i]; + this->item(i)->setStatusTip(QString().setNum(contact_records[i].id)); + } +} + +void ContactListWidget::add_contact() +{ + bool ok; + QString input=QInputDialog::getText(this,tr("Add New Contact"),tr("User ID: "),QLineEdit::Normal,NULL,&ok); + + if (ok && !input.isEmpty()) + { + quint32 user_id=input.toUInt(&ok); + + if (!ok) + { + QMessageBox mbx; + mbx.setIcon(QMessageBox::Warning); + mbx.setText(tr("Invalid user ID")); + mbx.exec(); + return; + } + + for(int i=0;i<contact_records.size();i++) + if (contact_records[i].id==user_id) + { + QMessageBox mbx; + mbx.setIcon(QMessageBox::Warning); + mbx.setText(tr("User \"(%1) %2\" already exists").arg(user_id).arg(contact_records[i].name)); + mbx.exec(); + return; + } + + Contacts::ContactRecord r; + + r.id=user_id; + r.name=""; + //void ContactListWidget::debug_onmsginput(QString msg, ChatWindow *sender_window) + //{ + // skynet->SendMessage(sender_window->getPeerId(), msg); + //} + + storage->Add(user_id); + + if (!storage->isOK()) + { + QMessageBox mbx; + mbx.setIcon(QMessageBox::Warning); + mbx.setText(storage->GetLastError()); + mbx.exec(); + return; + } + + addItem(QString("(%1) ").arg(user_id)); + contact_records.append(r); + wgitem_to_recs[this->item(this->count()-1)]=&contact_records.last(); + + } + +} + +void ContactListWidget::del_contact() +{ +QMessageBox mbx; +mbx.setText(QString(tr("Are you sure you want to remove user \"%1\" from your contact list?")).arg(currentItem()->text())); +mbx.setIcon(QMessageBox::Question); +mbx.addButton(QMessageBox::Yes); +mbx.addButton(QMessageBox::No); +mbx.setDefaultButton(QMessageBox::No); + +if (QMessageBox::Yes==mbx.exec()) + { + quint32 rm_id; + QListWidgetItem *rm_ptr; + + storage->Delete(rm_id = wgitem_to_recs[currentItem()]->id); + delete (rm_ptr=takeItem(currentRow())); + + for(int i=0;i<contact_records.size();i++) + { + if (contact_records[i].id==rm_id) + { + contact_records.removeAt(i); + break; + } + } + + wgitem_to_recs.remove(rm_ptr); + } + +} + +void ContactListWidget::start_chat() +{ +// ChatWindow *cw_debug_remove; + +// (cw_debug_remove = new ChatWindow(wgitem_to_recs[currentItem()]->id)) ->show();//debug + +// connect(cw_debug_remove, SIGNAL(msg_input(QString,ChatWindow*)), this, SLOT(debug_onmsginput(QString, ChatWindow*))); + + msguirouter->start_chat(wgitem_to_recs[currentItem()]->id); +} + +void ContactListWidget::view_cert() +{ + QString cert_pem = storage->GetContactCert(wgitem_to_recs[currentItem()]->id); + + if (!storage->isOK()) + { + QMessageBox mbx; + mbx.setText(storage->GetLastError()); + mbx.exec(); + } + + if (!cert_pem.isEmpty()) + { + ViewCertDialog vcd; + vcd.SetCert(cert_pem); + vcd.exec(); + } + else + { + QMessageBox mbx; + mbx.setText(tr("Certificate is not stored for this contact yet")); + mbx.exec(); + } +} diff --git a/pica-client/contactlistwidget.h b/pica-client/contactlistwidget.h new file mode 100644 index 0000000..d884bc4 --- /dev/null +++ b/pica-client/contactlistwidget.h @@ -0,0 +1,43 @@ +#ifndef CONTACTLISTWIDGET_H +#define CONTACTLISTWIDGET_H + +#include <QListWidget> +#include "contacts.h" +#include <QAction> + +#include "chatwindow.h" //debug remove + +class ContactListWidget : public QListWidget +{ + Q_OBJECT +public: + explicit ContactListWidget(QWidget *parent = 0); + ~ContactListWidget(); + + QAction *addcontactAct; + QAction *delcontactAct; + QAction *startchatAct; + QAction *viewcertAct; +protected: + void contextMenuEvent(QContextMenuEvent *event); + +private: + void setContactsStorage(Contacts *ct); + Contacts* storage; + QList<Contacts::ContactRecord> contact_records; + QMap<QListWidgetItem*,Contacts::ContactRecord*> wgitem_to_recs; + + //ChatWindow *cw_debug_remove; + +signals: + +private slots: + void add_contact(); + void del_contact(); + void start_chat(); + void view_cert(); + + //void debug_onmsginput(QString msg, ChatWindow *sender_window);//debug remove after debug +}; + +#endif // CONTACTLISTWIDGET_H diff --git a/pica-client/contacts.cpp b/pica-client/contacts.cpp new file mode 100644 index 0000000..7b66c4f --- /dev/null +++ b/pica-client/contacts.cpp @@ -0,0 +1,133 @@ +#include "contacts.h" + + +//#include <QMessageBox> + +Contacts::Contacts(QString storage, quint32 user_account_id) + : account_id_(user_account_id) +{ + dbconn=QSqlDatabase::addDatabase("QSQLITE"); + dbconn.setDatabaseName(storage); + + if (!dbconn.open()) + { + lasterr=dbconn.lastError(); + return; + } + +//check if table contacts exists +//... + + QSqlQuery query; + + query.exec("PRAGMA foreign_keys=ON;"); + + //check result, if foreign keys support is on +} + +bool Contacts::isOK() +{ + return !lasterr.isValid(); +} + +QString Contacts::GetLastError() +{ + return lasterr.text(); +} + +void Contacts::Add(quint32 id) +{ + QSqlQuery query; + + query.prepare("insert into contacts (id, name, cert_pem, account_id) values (:id, NULL, NULL, :account_id)"); + query.bindValue(":id",id); + query.bindValue(":account_id",account_id_); + query.exec(); + + lasterr=query.lastError(); +} + +void Contacts::Delete(quint32 id) +{ + QSqlQuery query; + + query.prepare("delete from contacts where id=:id and account_id=:account_id"); + query.bindValue(":id",id); + query.bindValue(":account_id",account_id_); + query.exec(); + + lasterr=query.lastError(); +} + +QList<Contacts::ContactRecord> Contacts::GetContacts() +{ + QList<ContactRecord> L; + ContactRecord r; + QSqlQuery query; + + query.setForwardOnly(true); + query.prepare("select id, name from contacts where account_id=:account_id"); + query.bindValue(":account_id",account_id_); + query.exec(); + lasterr=query.lastError(); + + if (lasterr.isValid()) + return L; + + while(query.next()) + { + r.id=query.value(0).toUInt(); + r.name=query.value(1).toString(); + //CSTRL.append(" ("+query.value(0).toString()+") "+query.value(1).toString()); + L<<r; + } + + return L; +} + +QString Contacts::GetContactCert(quint32 id) +{ + QSqlQuery query; + + query.prepare("select cert_pem from contacts where id=:id and account_id=:account_id"); + query.bindValue(":id",id); + query.bindValue(":account_id",account_id_); + query.exec(); + + lasterr=query.lastError(); + + if (lasterr.isValid()) + return QString::null; + + query.next(); + + return query.value(0).toString(); +} + +void Contacts::SetContactCert(quint32 id, QString &cert_pem) +{ + QSqlQuery query; + + query.prepare("update contacts set cert_pem=:cert_pem where id=:id and account_id=:account_id"); + query.bindValue(":cert_pem",cert_pem); + query.bindValue(":id",id); + query.bindValue(":account_id",account_id_); + query.exec(); + + lasterr=query.lastError(); + +} + +void Contacts::SetContactName(quint32 id, QString name) +{ + QSqlQuery query; + + query.prepare("update contacts set name=:name where id=:id and account_id=:account_id"); + query.bindValue(":name",name); + query.bindValue(":id",id); + query.bindValue(":account_id",account_id_); + query.exec(); + + lasterr=query.lastError(); +} + diff --git a/pica-client/contacts.h b/pica-client/contacts.h new file mode 100644 index 0000000..54042b5 --- /dev/null +++ b/pica-client/contacts.h @@ -0,0 +1,34 @@ +#ifndef CONTACTS_H +#define CONTACTS_H + +#include <QString> +#include <QtSql> +#include <QSqlDatabase> +#include <QStringList> + +class Contacts +{ +public: + struct ContactRecord + { + quint32 id; + QString name; + }; + + Contacts(QString storage, quint32 user_account_id); + + void Add(quint32 id); + void Delete(quint32 id); + QList<ContactRecord> GetContacts(); + QString GetContactCert(quint32 id); + void SetContactCert(quint32 id, QString &cert_pem); + void SetContactName(quint32 id, QString name); + QString GetLastError(); + bool isOK(); +private: + QSqlDatabase dbconn; + QSqlError lasterr; + quint32 account_id_; +}; + +#endif // CONTACTS_H diff --git a/pica-client/dialogs/addaccountdialog.cpp b/pica-client/dialogs/addaccountdialog.cpp new file mode 100644 index 0000000..86849d4 --- /dev/null +++ b/pica-client/dialogs/addaccountdialog.cpp @@ -0,0 +1,114 @@ +#include "addaccountdialog.h" +#include <QGridLayout> +#include <QLabel> +#include <QFile> +#include <QFileDialog> +#include <QMessageBox> +#include <QProcess> +#include "../PICA_client.h" + +AddAccountDialog::AddAccountDialog(QWidget *parent) : + QDialog(parent) +{ + QGridLayout *grid = new QGridLayout(this); + + QLabel *lb_cert_file = new QLabel(tr("Pica Pica Certificate File:")); + QLabel *lb_pkey_file = new QLabel(tr("Private Key File:")); + + btOK = new QPushButton(tr("OK")); + btCancel = new QPushButton(tr("Cancel")); + + cert_filename = new QLineEdit(); + pkey_filename = new QLineEdit(); + + bt_browse_cert = new QPushButton("..."); + bt_browse_pkey = new QPushButton("..."); + + rb_copyfiles = new QRadioButton(tr("Copy files to config directory")); + rb_readinplace = new QRadioButton(tr("Read files from selected place")); + + grid->addWidget(lb_cert_file,0,0,1,1); + + grid->addWidget(cert_filename,1,0,1,1); + grid->addWidget(bt_browse_cert,1,1,1,1); + + grid->addWidget(lb_pkey_file,2,0,1,1); + + grid->addWidget(pkey_filename,3,0,1,1); + grid->addWidget(bt_browse_pkey,3,1,1,1); + + grid->addWidget(rb_copyfiles,4,0,1,1); + grid->addWidget(rb_readinplace,5,0,1,1); + + grid->addWidget(btOK,6,0,1,1,Qt::AlignRight); + grid->addWidget(btCancel,6,1,1,1,Qt::AlignLeft); + //=========== + rb_copyfiles->setChecked(true); + + btOK->setMaximumWidth(90); + btCancel->setMaximumWidth(90); + + connect(btOK,SIGNAL(clicked()),this,SLOT(OK())); + connect(btCancel,SIGNAL(clicked()),this,SLOT(Cancel())); + connect(bt_browse_cert,SIGNAL(clicked()),this,SLOT(browse_cert())); + connect(bt_browse_pkey,SIGNAL(clicked()),this,SLOT(browse_pkey())); + + +} + +void AddAccountDialog::OK() +{ + { + QString s; + if (!QFile::exists(s=cert_filename->text()) || !QFile::exists(s=pkey_filename->text()) ) + { + QMessageBox mbx; + mbx.setText(tr("File %1 does not exist").arg(s)); + mbx.exec(); + return; + } + + quint32 id; + + if (!PICA_get_id_from_cert(cert_filename->text().toUtf8().constData(),&id)) + { + QMessageBox mbx; + mbx.setText(tr("Invalid certificate file")); + mbx.exec(); + return; + } + } + + done(1); + +} + +void AddAccountDialog::Cancel() +{ + done(0); +} + +void AddAccountDialog::browse_cert() +{ + cert_filename->setText(QFileDialog::getOpenFileName(this,tr("Select Pica Pica Certificate"),"","PEM file (*.pem)")); +} + +void AddAccountDialog::browse_pkey() +{ + pkey_filename->setText(QFileDialog::getOpenFileName(this,tr("Select Private Key"),"","PEM file (*.pem)")); +} + +QString AddAccountDialog::GetCertFilename() +{ + return cert_filename->text(); +} + +QString AddAccountDialog::GetPkeyFilename() +{ + return pkey_filename->text(); +} + +bool AddAccountDialog::isCopyFilesChecked() +{ + return rb_copyfiles->isChecked(); +} diff --git a/pica-client/dialogs/addaccountdialog.h b/pica-client/dialogs/addaccountdialog.h new file mode 100644 index 0000000..976d020 --- /dev/null +++ b/pica-client/dialogs/addaccountdialog.h @@ -0,0 +1,41 @@ +#ifndef ADDACCOUNTDIALOG_H +#define ADDACCOUNTDIALOG_H + +#include <QDialog> +#include <QPushButton> +#include <QLineEdit> +#include <QRadioButton> + +class AddAccountDialog : public QDialog +{ + Q_OBJECT +public: + explicit AddAccountDialog(QWidget *parent = 0); + QString GetCertFilename(); + QString GetPkeyFilename(); + bool isCopyFilesChecked(); + +private: + QPushButton *btOK; + QPushButton *btCancel; + + QLineEdit *cert_filename; + QLineEdit *pkey_filename; + + QPushButton *bt_browse_cert; + QPushButton *bt_browse_pkey; + + QRadioButton *rb_copyfiles; + QRadioButton *rb_readinplace; +signals: + +public slots: +private slots: + void OK(); + void Cancel(); + void browse_cert(); + void browse_pkey(); + +}; + +#endif // ADDACCOUNTDIALOG_H diff --git a/pica-client/dialogs/forgedcertdialog.cpp b/pica-client/dialogs/forgedcertdialog.cpp new file mode 100644 index 0000000..5ca7f23 --- /dev/null +++ b/pica-client/dialogs/forgedcertdialog.cpp @@ -0,0 +1,53 @@ +#include "forgedcertdialog.h" +#include "viewcertdialog.h" +#include <QGridLayout> +#include <QLabel> + +ForgedCertDialog::ForgedCertDialog(quint32 peer_id, QString received_cert, QString stored_cert, QWidget *parent) : + QDialog(parent), + received_cert_(received_cert), + stored_cert_(stored_cert) +{ + QGridLayout *grid = new QGridLayout(this); + + QLabel *scary_text = new QLabel(tr( +"Previously stored and currently present certificates for user %1 do not match.\n\ +This means that probably someone is trying to eavesdrop your communication\n\ +using man-in-the-middle attack or impersonate the user.\n\ +Connection aborted.").arg(peer_id)); + + btOK = new QPushButton(tr("OK")); + btShow_stored_cert = new QPushButton(tr("View Stored Certificate...")); + btShow_received_cert = new QPushButton(tr("View Received Certificate...")); + + grid->addWidget(scary_text, 0,0, 1, 3, Qt::AlignCenter); + grid->addWidget(btShow_stored_cert, 1, 0, Qt::AlignCenter); + grid->addWidget(btShow_received_cert, 1, 2, Qt::AlignCenter); + grid->addWidget(btOK, 2, 1, Qt::AlignCenter); + + + setLayout(grid); + + connect(btOK, SIGNAL(clicked()), this, SLOT(OK())); + connect(btShow_stored_cert, SIGNAL(clicked()), this, SLOT(StoredCert())); + connect(btShow_received_cert, SIGNAL(clicked()), this, SLOT(ReceivedCert())); +} + +void ForgedCertDialog::OK() +{ + done(0); +} + +void ForgedCertDialog::ReceivedCert() +{ + ViewCertDialog vcd; + vcd.SetCert(received_cert_); + vcd.exec(); +} + +void ForgedCertDialog::StoredCert() +{ + ViewCertDialog vcd; + vcd.SetCert(stored_cert_); + vcd.exec(); +} diff --git a/pica-client/dialogs/forgedcertdialog.h b/pica-client/dialogs/forgedcertdialog.h new file mode 100644 index 0000000..bc259d4 --- /dev/null +++ b/pica-client/dialogs/forgedcertdialog.h @@ -0,0 +1,34 @@ +#ifndef FORGEDCERTDIALOG_H +#define FORGEDCERTDIALOG_H + +#include <QDialog> +#include <QPushButton> +#include "viewcertdialog.h" + +class ForgedCertDialog : public QDialog +{ + Q_OBJECT +public: + explicit ForgedCertDialog(quint32 peer_id, QString received_cert, QString stored_cert, QWidget *parent = 0); + +private: + QString received_cert_; + QString stored_cert_; + + QPushButton *btOK; + QPushButton *btShow_stored_cert; + QPushButton *btShow_received_cert; + + +signals: + +public slots: + +private slots: + void OK(); + void StoredCert(); + void ReceivedCert(); + +}; + +#endif // FORGEDCERTDIALOG_H diff --git a/pica-client/dialogs/viewcertdialog.cpp b/pica-client/dialogs/viewcertdialog.cpp new file mode 100644 index 0000000..8b096c2 --- /dev/null +++ b/pica-client/dialogs/viewcertdialog.cpp @@ -0,0 +1,68 @@ +#include "viewcertdialog.h" +#include <QVBoxLayout> +#include <QProcess> + +#include <QMessageBox> + +#include <unistd.h> +#include <stdio.h> +#include <sys/wait.h> +#include <QFile> + +ViewCertDialog::ViewCertDialog(QWidget *parent) : + QDialog(parent) +{ + QVBoxLayout *layout; + + layout = new QVBoxLayout; + cert_text = new QTextEdit(this); + btOK = new QPushButton(tr("OK")); + btOK->setMaximumWidth(45); + this->setMinimumWidth(640); + this->setMinimumHeight(480); + + QTextCharFormat fmt; + + fmt = cert_text->currentCharFormat(); + fmt.setFontFixedPitch(true); + fmt.setFontStyleHint(QFont::Monospace); + fmt.setFontFamily("Liberation Mono"); + cert_text->setCurrentCharFormat(fmt); + + cert_text->setReadOnly(true); + + layout->addWidget(cert_text); + layout->addWidget(btOK, 0, Qt::AlignCenter); + + setLayout(layout); + + connect(btOK, SIGNAL(clicked()), this, SLOT(OK())); + +} + +void ViewCertDialog::SetCert(QString cert_pem) +{ + QProcess openssl(this); + qint64 ret; + char *data; + QByteArray ba; + + openssl.start("openssl", QStringList()<<"x509"<<"-noout"<<"-text"<<"-nameopt"<<"multiline,-esc_msb,utf8"); + + if (!openssl.waitForStarted()) + return; + + ba = cert_pem.toAscii(); + ret = openssl.write(data = (char*)ba.constData()); + openssl.closeWriteChannel(); + + if (!openssl.waitForFinished()) + return; + + cert_text->setPlainText(QString::fromUtf8(openssl.readAllStandardOutput().constData())); +} + +void ViewCertDialog::OK() +{ + done(0); +} diff --git a/pica-client/dialogs/viewcertdialog.h b/pica-client/dialogs/viewcertdialog.h new file mode 100644 index 0000000..013815c --- /dev/null +++ b/pica-client/dialogs/viewcertdialog.h @@ -0,0 +1,28 @@ +#ifndef VIEWCERTDIALOG_H +#define VIEWCERTDIALOG_H + +#include <QDialog> +#include <QTextEdit> +#include <QPushButton> + +class ViewCertDialog : public QDialog +{ + Q_OBJECT +public: + explicit ViewCertDialog(QWidget *parent = 0); + void SetCert(QString cert_pem); + +private: + QTextEdit *cert_text; + QPushButton *btOK; + +signals: + +public slots: + +private slots: + void OK(); + +}; + +#endif // VIEWCERTDIALOG_H diff --git a/pica-client/globals.h b/pica-client/globals.h new file mode 100644 index 0000000..b6c2d7d --- /dev/null +++ b/pica-client/globals.h @@ -0,0 +1,26 @@ +#ifndef GLOBALS_H +#define GLOBALS_H + +#define PICA_CLIENT_STORAGEDB "pica-client.sqlite" +#define PICA_CLIENT_CONFIGDIR ".pica-client" + +#ifndef PICA_INSTALLPREFIX + +#define PICA_INSTALLPREFIX "/usr/" + +#endif + +//variables are defined in main.cpp +extern class QString config_dir; +extern class QString config_dbname; +extern class QString config_defaultCA; +extern class SkyNet *skynet; +extern class MainWindow *mainwindow; +extern unsigned int account_id; +extern class AskPassword *askpassword; +extern class MsgUIRouter *msguirouter; +extern class QIcon picapica_ico_sit; +extern class QIcon picapica_ico_fly; +extern class PicaActionCenter *action_center; +extern class PicaSysTray *systray; +#endif // GLOBALS_H diff --git a/pica-client/main.cpp b/pica-client/main.cpp new file mode 100644 index 0000000..f7a725b --- /dev/null +++ b/pica-client/main.cpp @@ -0,0 +1,161 @@ +#include <QtGui/QApplication> +#include "mainwindow.h" +#include "accountswindow.h" +#include "globals.h" +#include "contacts.h" +#include "skynet.h" +#include "msguirouter.h" +#include "picaactioncenter.h" +#include "picasystray.h" +#include <QDir> +#include <QMessageBox> +#include <QString> +#include <QIcon> + +//globals +QString config_dir; +QString config_dbname; +SkyNet *skynet; +MainWindow *mainwindow; +unsigned int account_id; +class AskPassword *askpassword; +class MsgUIRouter *msguirouter; + +QIcon picapica_ico_sit; +QIcon picapica_ico_fly; + +class PicaActionCenter *action_center; +class PicaSysTray *systray; + +// /usr/share/pica-client/pica-pica_CA.pem +//QString config_defaultCA("/home/root_jr/files/projects/picapica_wc/localhost/project_picapica/trunk/tests/trusted_CA.pem"); +QString config_defaultCA(PICA_INSTALLPREFIX"share/pica-client/trusted_CA.pem"); + +static bool create_config_dir() +{ + QMessageBox msgBox; + config_dir=QDir::homePath()+QDir::separator() +QString(PICA_CLIENT_CONFIGDIR); + + if (!QFile::exists(config_dir)) + { + msgBox.setText(QString("%1 does not exist").arg(config_dir));//debug + msgBox.exec(); + + QDir dir; + + if (!dir.mkpath(config_dir)) + { + msgBox.setText(QString(QObject::tr("Unable to create configuration directory:\n %1")).arg(config_dir)); + msgBox.exec(); + return false; + } + } + + config_dbname=config_dir+QDir::separator()+QString(PICA_CLIENT_STORAGEDB); + + if (!QFile::exists(config_dbname)) + { + QSqlDatabase dbconn=QSqlDatabase::addDatabase("QSQLITE"); + dbconn.setDatabaseName(config_dbname); + + if (!dbconn.open()) + { + + msgBox.setText(dbconn.lastError().text()); + msgBox.exec(); + return false; + } + + QSqlQuery query; + + query.exec("PRAGMA foreign_keys=ON;"); + + if (query.lastError().isValid()) + goto showerror; + + query.exec("create table accounts \ + (\ + id int primary key, \ + name varchar(64), \ + cert_file varchar(255) not null, \ + pkey_file varchar(255) not null, \ + ca_file varchar(255) default null \ + );"); + + if (query.lastError().isValid()) + goto showerror; + + query.exec("create table contacts \ + (\ + id int,\ + name varchar(64), \ + cert_pem text(2048), \ + account_id int not null, \ + primary key(id, account_id),\ + foreign key(account_id) references accounts(id) on delete cascade\ + );"); + + if (query.lastError().isValid()) + goto showerror; + + query.exec( + "create table nodes \ + (\ + address varchar(255) not null,\ + port int not null,\ + last_active int not null,\ + inactive_count int not null,\ + constraint pk primary key (address,port) on conflict replace \ + );"); + + + if (query.lastError().isValid()) + showerror: + { + msgBox.setText(query.lastError().text()); + msgBox.exec(); + return false; + } + + } + + + return true; + + +} + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + if (!create_config_dir()) + return -1; + + picapica_ico_sit = QIcon(PICA_INSTALLPREFIX"share/pica-client/picapica-icon-sit.png"); + picapica_ico_fly = QIcon(PICA_INSTALLPREFIX"share/pica-client/picapica-icon-fly.png"); + + PicaActionCenter ac; + action_center = ∾ + + AccountsWindow aw; + + SkyNet s; + skynet=&s; + + PicaSysTray pst; + systray = &pst; + + AskPassword askpsw; + askpassword=&askpsw; + + MsgUIRouter muir; + msguirouter = &muir; + + + aw.show(); + + a.setQuitOnLastWindowClosed(false); + + return a.exec(); +} diff --git a/pica-client/mainwindow.cpp b/pica-client/mainwindow.cpp new file mode 100644 index 0000000..5bf44dd --- /dev/null +++ b/pica-client/mainwindow.cpp @@ -0,0 +1,110 @@ +#include "mainwindow.h" +#include "globals.h" +#include "skynet.h" +#include "picaactioncenter.h" +#include "accounts.h" +#include "ui_mainwindow.h" +#include <QVBoxLayout> +#include <QMessageBox> + + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + QVBoxLayout *layout; + + layout = new QVBoxLayout; + contact_list = new ContactListWidget; + status = new QComboBox; + + status->setMinimumHeight(36); + + layout->addWidget(contact_list); + layout->addWidget(status,0,Qt::AlignBottom); + + centralWidget()->setLayout(layout); + setWindowIcon(picapica_ico_sit); + + createMenus(); + + + //for (int i=0;i<255;i++) + // contact_list->addItem(QString("test %1").arg(i)); + + status->addItem(picapica_ico_fly, QString(tr("Connected"))); + status->addItem(picapica_ico_sit, QString(tr("Disconnected"))); + + connect(skynet, SIGNAL(BecameSelfAware()), this, SLOT(set_online())); + connect(skynet, SIGNAL(LostSelfAwareness()), this, SLOT(set_offline())); + connect(status, SIGNAL(currentIndexChanged(int)), this, SLOT(status_changed(int))); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +void MainWindow::set_offline() +{ + SetStatus(false); +} + +void MainWindow::set_online() +{ + SetStatus(true); +} + +void MainWindow::status_changed(int index) +{ + if (status_change_disable_flag) + return; + + if (index == 0 && !skynet->isSelfAware()) + { + Accounts::AccountRecord curr_acc = skynet->CurrentAccount(); + skynet->Join(curr_acc); + } + if (1 == index && skynet->isSelfAware()) + skynet->Exit(); +} + +void MainWindow::SetStatus(bool connected) +{ + status_change_disable_flag = true; + + if (connected) + { + status->setCurrentIndex(0); + setWindowTitle(tr("Pica Pica Messenger - ") + QString::number(account_id)); + } + else + { + status->setCurrentIndex(1); + setWindowTitle(tr("Pica Pica Messenger - Disconnected")); + } + + status_change_disable_flag = false; +} + + +void MainWindow::createMenus() +{ + contactsMenu=menuBar()->addMenu(tr("&Contacts")); + contactsMenu->addAction(contact_list->addcontactAct); + contactsMenu->addSeparator(); + contactsMenu->addAction(action_center->ExitAct()); + + nodesMenu=menuBar()->addMenu(tr("&Nodes")); + + helpMenu=menuBar()->addMenu(tr("&Help")); + helpMenu->addAction(action_center->AboutAct()); + +} + + + + + diff --git a/pica-client/mainwindow.h b/pica-client/mainwindow.h new file mode 100644 index 0000000..3361c69 --- /dev/null +++ b/pica-client/mainwindow.h @@ -0,0 +1,48 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QListWidget> +#include <QComboBox> +#include <QSystemTrayIcon> + +#include "contacts.h" +#include "contactlistwidget.h" +#include "askpassword.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +protected: + +private: + void createMenus(); + + Ui::MainWindow *ui; + ContactListWidget *contact_list; + QComboBox *status; + + QMenu *contactsMenu; + QMenu *helpMenu; + QMenu *nodesMenu; + + bool status_change_disable_flag; + void SetStatus(bool connected); + +private slots: + void set_online(); + void set_offline(); + void status_changed(int index); + +}; + +#endif // MAINWINDOW_H diff --git a/pica-client/mainwindow.ui b/pica-client/mainwindow.ui new file mode 100644 index 0000000..0f2e2a7 --- /dev/null +++ b/pica-client/mainwindow.ui @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>442</width> + <height>565</height> + </rect> + </property> + <property name="windowTitle"> + <string>Pica Pica Messenger</string> + </property> + <widget class="QWidget" name="centralWidget"/> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>442</width> + <height>22</height> + </rect> + </property> + </widget> + <widget class="QToolBar" name="mainToolBar"> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + </widget> + <widget class="QStatusBar" name="statusBar"/> + </widget> + <layoutdefault spacing="6" margin="11"/> + <resources/> + <connections/> +</ui> diff --git a/pica-client/msguirouter.cpp b/pica-client/msguirouter.cpp new file mode 100644 index 0000000..efa080c --- /dev/null +++ b/pica-client/msguirouter.cpp @@ -0,0 +1,93 @@ +#include "msguirouter.h" +#include "dialogs/forgedcertdialog.h" + + +/* + этот класс должен обеспечивать связь между приёмом-отправкой сообщений и их отображением в пользовательском интерфейсе. + Класс SkyNet отвечает только за прием и отправку сообщений, пересылку через сеть и работу с протоколом, ему пофиг, + как они показываются юзеру. Данный класс должен работать с GUI, например, по приходу нового сообщения (через сигнал от + класса SkyNet) либо помигать иконкой в трее, либо создать новое окно чата, либо развернуть уже имеющееся, либо помигать + свернутым окном на таскбаре. Это поведение может настраиваться, настройки хранить в базе и загружать в конструкторе из + базы sqlite. Например, данный класс должен управлять экземплярами ChatWindow, создавать новые при необходимости либо + активировать имеющиеся. Также этот класс должен обеспечивать хранение отложенных сообщений (когда собеседник недоступен). + Можно придумать название получше + */ +MsgUIRouter::MsgUIRouter(QObject *parent) : + QObject(parent) +{ + connect(skynet, SIGNAL(MessageReceived(quint32,QString)), this, SLOT(msg_from_peer(quint32,QString))); + connect(skynet, SIGNAL(UnableToDeliver(quint32,QString)), this, SLOT(delivery_failed(quint32,QString))); + connect(skynet, SIGNAL(Delivered(quint32)), this, SLOT(delivered(quint32))); + connect(skynet, SIGNAL(CertificateForged(quint32,QString,QString)), this, SLOT(scary_cert_message(quint32,QString,QString))); + +} + +void MsgUIRouter::create_chatwindow(quint32 peer_id) +{ + ChatWindow *cw; + + cw = new ChatWindow(peer_id); + + chatwindows[peer_id] = cw; + + connect(cw, SIGNAL(msg_input(QString,ChatWindow*)), this, SLOT(msg_to_peer(QString,ChatWindow*))); + connect(cw, SIGNAL(chatwindow_close(ChatWindow*)), this, SLOT(chatwindow_closed(ChatWindow*))); + + cw->show(); +} + +void MsgUIRouter::msg_from_peer(quint32 from, QString msg) +{ + + if ( ! chatwindows.contains(from)) + { + create_chatwindow(from); + } + + chatwindows[from]->msg_from_peer(msg); + +} + +void MsgUIRouter::msg_to_peer(QString msg, ChatWindow *sender_window) +{ + skynet->SendMessage(sender_window->getPeerId(), msg); +} + +void MsgUIRouter::start_chat(quint32 peer_id) +{ + if ( ! chatwindows.contains(peer_id)) + { + create_chatwindow(peer_id); + } + else + { + chatwindows[peer_id]->showNormal(); + } +} + +void MsgUIRouter::chatwindow_closed(ChatWindow *sender_window) +{ + chatwindows.remove(sender_window->getPeerId()); +} + +void MsgUIRouter::delivery_failed(quint32 to, QString msg) +{ +// if (chatwindows.contains(to)) +// { +// chatwindows[to]->msg_informational(tr("failed to deliver")); +// } +} + +void MsgUIRouter::delivered(quint32 to) +{ + if (chatwindows.contains(to)) + { + chatwindows[to]->msg_delivered(); + } +} + +void MsgUIRouter::scary_cert_message(quint32 peer_id, QString received_cert, QString stored_cert) +{ + ForgedCertDialog fcd(peer_id, received_cert, stored_cert); + fcd.exec(); +} diff --git a/pica-client/msguirouter.h b/pica-client/msguirouter.h new file mode 100644 index 0000000..5988878 --- /dev/null +++ b/pica-client/msguirouter.h @@ -0,0 +1,37 @@ +#ifndef MSGUIROUTER_H +#define MSGUIROUTER_H + +#include <QObject> +#include <QList> +#include <QMap> +#include "chatwindow.h" +#include "skynet.h" +#include "globals.h" + +class MsgUIRouter : public QObject +{ + Q_OBJECT +public: + explicit MsgUIRouter(QObject *parent = 0); + +signals: + +public slots: + void msg_to_peer(QString msg, ChatWindow *sender_window); + void msg_from_peer(quint32 from, QString msg); + + void chatwindow_closed(ChatWindow *sender_window); + void start_chat(quint32 peer_id); + + void delivery_failed(quint32 to, QString msg); + void delivered(quint32 to); + + void scary_cert_message(quint32 peer_id, QString received_cert, QString stored_cert); +private: + QMap<quint32,ChatWindow*> chatwindows; + + void create_chatwindow(quint32 peer_id); + +}; + +#endif // MSGUIROUTER_H diff --git a/pica-client/nodes.cpp b/pica-client/nodes.cpp new file mode 100644 index 0000000..eb8bfcc --- /dev/null +++ b/pica-client/nodes.cpp @@ -0,0 +1,73 @@ +#include "nodes.h" + +Nodes::Nodes(QString storage) +{ + dbconn=QSqlDatabase::addDatabase("QSQLITE"); + dbconn.setDatabaseName(storage); + + if (!dbconn.open()) + { + lasterr=dbconn.lastError(); + return; + } +//check if table nodes exists +} + +void Nodes::Add(NodeRecord &n) +{ + QSqlQuery query; + + query.prepare("insert into nodes (address, port, last_active, inactive_count) values (:addr, :port, strftime('%s','now'), 0)"); + query.bindValue(":addr",n.address); + query.bindValue(":port",n.port); + query.exec(); + + lasterr=query.lastError(); +} + +QList<Nodes::NodeRecord> Nodes::GetNodes() +{ + QList<NodeRecord> L; + NodeRecord r; + QSqlQuery query; + + query.setForwardOnly(true); + query.exec("select address, port from nodes order by inactive_count asc, last_active desc"); + lasterr=query.lastError(); + + if (lasterr.isValid()) + return L; + + while(query.next()) + { + r.address =query.value(0).toString(); + r.port =query.value(1).toUInt(); + + L<<r; + } + + return L; +} + +void Nodes::MakeClean() +{ + +} + +void Nodes::UpdateStatus(NodeRecord &n, bool alive) +{ + QSqlQuery query; + + if (alive) + query.prepare("update nodes set last_active = strftime('%s','now'), inactive_count = 0 where address = :addr and port = :port"); + else + query.prepare("update nodes set inactive_count = inactive_count + 1 where address = :addr and port = :port"); + + query.bindValue(":addr",n.address); + query.bindValue(":port",n.port); + query.exec(); + + lasterr=query.lastError(); +} + + diff --git a/pica-client/nodes.h b/pica-client/nodes.h new file mode 100644 index 0000000..a24fed2 --- /dev/null +++ b/pica-client/nodes.h @@ -0,0 +1,30 @@ +#ifndef NODES_H +#define NODES_H + +#include <QList> +#include <QString> +#include <QtSql> +#include <QSqlDatabase> + +class Nodes +{ +public: + struct NodeRecord + { + QString address; + quint16 port; + }; + + Nodes(QString storage); + void Add(NodeRecord &n); + QList<NodeRecord> GetNodes(); + void MakeClean();//remove old inactive node records + void UpdateStatus(NodeRecord &n, bool alive); + +private: + QSqlDatabase dbconn; + QSqlError lasterr; + +}; + +#endif // NODES_H diff --git a/pica-client/nodethread.cpp b/pica-client/nodethread.cpp new file mode 100644 index 0000000..1524cbd --- /dev/null +++ b/pica-client/nodethread.cpp @@ -0,0 +1,94 @@ +#include "nodethread.h" +#include "mainwindow.h" + +QMutex NodeThread::finished_mutex; + +NodeThread::NodeThread(Nodes::NodeRecord &addr, bool *completed, Accounts::AccountRecord &acc, PICA_conninfo **pica_conn, QMutex *wm) : + QThread(0) +{ + node_addr = addr; + finished_flag = completed; + user_account = acc; + connection = pica_conn; + write_mutex = wm; + exit = 0; + start(); +} + +void NodeThread::run() +{ + int ret; + exit = 0; + + ret = PICA_new_connection(node_addr.address.toUtf8().constData(), + node_addr.port, + user_account.CA_file.toUtf8().constData(), + user_account.cert_file.toUtf8().constData(), + user_account.pkey_file.toUtf8().constData(), + AskPassword::ask_password_cb, + &ci); + + switch(ret) + { + case PICA_OK: + + emit NodeStatusChanged(node_addr.address, node_addr.port, true); + + finished_mutex.lock(); + if (*finished_flag) + { + PICA_close_connection(ci); + exit = 1; + } + else + { + *finished_flag = true; + *connection = ci; + emit ConnectedToNode(node_addr.address, node_addr.port, this); + } + finished_mutex.unlock(); + + + + break; + + case PICA_ERRSOCK: + case PICA_ERRDNSRESOLVE: + + emit NodeStatusChanged(node_addr.address, node_addr.port, false); + + exit = 1; + break; + + case PICA_ERRSSL: + exit = 1; + break; + + default: + exit = 1; + } + + if (exit) + return; + + do + { + write_mutex->lock(); + if (PICA_read(ci,10000) != PICA_OK || PICA_write(ci) != PICA_OK) + exit=1; + write_mutex->unlock(); + msleep(300); + } + while (!exit); + + write_mutex->lock(); + PICA_close_connection(ci); + *connection = NULL; + write_mutex->unlock(); +} + +void NodeThread::CloseThread() +{ + exit = 1; +} + diff --git a/pica-client/nodethread.h b/pica-client/nodethread.h new file mode 100644 index 0000000..1d17fda --- /dev/null +++ b/pica-client/nodethread.h @@ -0,0 +1,40 @@ +#ifndef NODETHREAD_H +#define NODETHREAD_H + +#include <QThread> +#include <QMutex> +#include "nodes.h" +#include "../PICA_client.h" +#include "accounts.h" + + +class NodeThread : public QThread +{ + Q_OBJECT +public: + explicit NodeThread( Nodes::NodeRecord &addr, bool *completed,Accounts::AccountRecord &acc,struct PICA_conninfo **pica_conn, QMutex *wm); + void CloseThread(); +private: + void run(); + static QMutex finished_mutex; + + QMutex *write_mutex; + + bool *finished_flag; + Nodes::NodeRecord node_addr; + struct PICA_conninfo *ci; + Accounts::AccountRecord user_account; + struct PICA_conninfo **connection; + + int exit; + + +signals: + void NodeStatusChanged(QString addr, quint16 port, bool alive); + void ConnectedToNode(QString addr, quint16 port, NodeThread *thread); + +public slots: + +}; + +#endif // NODETHREAD_H diff --git a/pica-client/pica-client.pro b/pica-client/pica-client.pro new file mode 100644 index 0000000..30c401e --- /dev/null +++ b/pica-client/pica-client.pro @@ -0,0 +1,70 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2011-10-22T02:19:02 +# +#------------------------------------------------- + +QT += core gui sql + +TARGET = pica-client +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + chatwindow.cpp \ + contacts.cpp \ + ../PICA_client.c \ + ../PICA_msgproc.c \ + contactlistwidget.cpp \ + skynet.cpp \ + nodes.cpp \ + accountswindow.cpp \ + accounts.cpp \ + dialogs/addaccountdialog.cpp \ + nodethread.cpp \ + askpassword.cpp \ + msguirouter.cpp \ + picasystray.cpp \ + picaactioncenter.cpp \ + dialogs/viewcertdialog.cpp \ + dialogs/forgedcertdialog.cpp + +HEADERS += mainwindow.h \ + chatwindow.h \ + contacts.h \ + globals.h \ + ../PICA_client.h \ + ../PICA_msgproc.h \ + contactlistwidget.h \ + skynet.h \ + nodes.h \ + accountswindow.h \ + accounts.h \ + dialogs/addaccountdialog.h \ + nodethread.h \ + askpassword.h \ + msguirouter.h \ + picasystray.h \ + picaactioncenter.h \ + dialogs/viewcertdialog.h \ + dialogs/forgedcertdialog.h \ + ../PICA_common.h \ + ../PICA_proto.h + + +DEFINES = PICA_MULTITHREADED + +FORMS += mainwindow.ui + + + +unix: CONFIG += link_pkgconfig +unix: PKGCONFIG += libssl + +unix: CONFIG += link_pkgconfig +unix: PKGCONFIG += libcrypto + + + + diff --git a/pica-client/picaactioncenter.cpp b/pica-client/picaactioncenter.cpp new file mode 100644 index 0000000..9a811dc --- /dev/null +++ b/pica-client/picaactioncenter.cpp @@ -0,0 +1,37 @@ +#include "picaactioncenter.h" +#include "globals.h" +#include "mainwindow.h" +#include "skynet.h" +#include <QApplication> + +#include <QMessageBox> + +PicaActionCenter::PicaActionCenter(QObject *parent) : + QObject(parent) +{ + exitAct = new QAction(tr("E&xit"),this); + exitAct->setStatusTip(tr("Close Pica Pica messenger and exit")); + connect(exitAct,SIGNAL(triggered()),this,SLOT(exit())); + + aboutAct = new QAction(tr("A&bout"),this); + aboutAct->setStatusTip(tr("About Pica Pica Messenger")); + connect(aboutAct,SIGNAL(triggered()),this,SLOT(about())); +} + +void PicaActionCenter::about() +{ + QMessageBox mbx; + mbx.setWindowIcon(picapica_ico_sit); + mbx.setIconPixmap(picapica_ico_sit.pixmap(32)); + mbx.setText(tr("(c) 2012 Anton Sviridenko")); + mbx.exec(); +} + +void PicaActionCenter::exit() +{ + if (mainwindow) + mainwindow->close(); + + skynet->Exit(); + QApplication::instance()->quit(); +} diff --git a/pica-client/picaactioncenter.h b/pica-client/picaactioncenter.h new file mode 100644 index 0000000..a406743 --- /dev/null +++ b/pica-client/picaactioncenter.h @@ -0,0 +1,28 @@ +#ifndef PICAACTIONCENTER_H +#define PICAACTIONCENTER_H + +#include <QObject> +#include <QAction> + +class PicaActionCenter : public QObject +{ + Q_OBJECT +public: + explicit PicaActionCenter(QObject *parent = 0); + + QAction *AboutAct() {return aboutAct;}; + QAction *ExitAct() {return exitAct;}; + +private: + QAction *aboutAct; + QAction *exitAct; + +signals: + +public slots: +private slots: + void about(); + void exit(); +}; + +#endif // PICAACTIONCENTER_H diff --git a/pica-client/picapica-icon-fly.png b/pica-client/picapica-icon-fly.png Binary files differnew file mode 100644 index 0000000..b01e972 --- /dev/null +++ b/pica-client/picapica-icon-fly.png diff --git a/pica-client/picapica-icon-sit.png b/pica-client/picapica-icon-sit.png Binary files differnew file mode 100644 index 0000000..6dfc5c9 --- /dev/null +++ b/pica-client/picapica-icon-sit.png diff --git a/pica-client/picasystray.cpp b/pica-client/picasystray.cpp new file mode 100644 index 0000000..ab31887 --- /dev/null +++ b/pica-client/picasystray.cpp @@ -0,0 +1,39 @@ +#include "picasystray.h" +#include "globals.h" +#include "picaactioncenter.h" + +PicaSysTray::PicaSysTray(QObject *parent) : + QObject(parent) +{ + systrayMenu_ = new QMenu(); + + systrayMenu_->addAction(action_center->AboutAct()); + systrayMenu_->addSeparator(); + systrayMenu_->addAction(action_center->ExitAct()); + + systray_ = new QSystemTrayIcon(picapica_ico_sit); + systray_->setContextMenu(systrayMenu_); + systray_->show(); + + connect(systray_, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systray_activated(QSystemTrayIcon::ActivationReason))); + connect(skynet, SIGNAL(BecameSelfAware()), this, SLOT(skynet_became_self_aware())); + connect(skynet, SIGNAL(LostSelfAwareness()), this, SLOT(skynet_lost_self_awareness())); +} + +void PicaSysTray::systray_activated(QSystemTrayIcon::ActivationReason r) +{ + if (r == QSystemTrayIcon::DoubleClick && mainwindow!=NULL) + { + mainwindow->showNormal(); + } +} + +void PicaSysTray::skynet_became_self_aware() +{ + systray_->setIcon(picapica_ico_fly); +} + +void PicaSysTray::skynet_lost_self_awareness() +{ + systray_->setIcon(picapica_ico_sit); +} diff --git a/pica-client/picasystray.h b/pica-client/picasystray.h new file mode 100644 index 0000000..cf9706d --- /dev/null +++ b/pica-client/picasystray.h @@ -0,0 +1,31 @@ +#ifndef PICASYSTRAY_H +#define PICASYSTRAY_H + +#include <QObject> +#include <QSystemTrayIcon> +#include <QMenu> +#include "skynet.h" +#include "mainwindow.h" +#include "globals.h" + +class PicaSysTray : public QObject +{ + Q_OBJECT +public: + explicit PicaSysTray(QObject *parent = 0); + +signals: + +public slots: + +private: + QSystemTrayIcon *systray_; + QMenu *systrayMenu_; + +private slots: + void systray_activated(QSystemTrayIcon::ActivationReason); + void skynet_became_self_aware();//debug + void skynet_lost_self_awareness();//debug +}; + +#endif // PICASYSTRAY_H diff --git a/pica-client/skynet.cpp b/pica-client/skynet.cpp new file mode 100644 index 0000000..d522d60 --- /dev/null +++ b/pica-client/skynet.cpp @@ -0,0 +1,437 @@ +#include "skynet.h" +#include "globals.h" +#include <QMutex> +//#include "dialogs/viewcertdialog.h" +#include "contacts.h" +//#include <QMessageBox> //debug +//#include "dialogs/forgedcertdialog.h" +#include <QDebug> + + +SkyNet::SkyNet() + : nodes(config_dbname), QObject(0) +{ + self_aware = false; + +PICA_client_callbacks cbs = { + newmsg_cb, + msgok_cb, + channel_established_cb, + channel_failed, + accept_cb, + notfound_cb, + channel_closed_cb, + nodelist_cb, + peer_cert_verify_cb + }; + +PICA_client_init(&cbs); + +nodelink = NULL; + +connect(this, SIGNAL(PeerCertificateReceived(quint32,QString,bool*)), this, SLOT(verify_peer_cert(quint32,QString,bool*)),Qt::BlockingQueuedConnection); + +} + +void SkyNet::nodethread_finished() +{ +//удалить здесь завершившийся поток + for (int i=0;i<threads.count();i++) + { + if (threads[i]->isFinished()) + { + delete threads.takeAt(i); + break; + } + } + + if (threads.count() == 0) + { + self_aware = false; + emit LostSelfAwareness(); + } +} + +void SkyNet::nodethread_connected(QString addr, quint16 port, NodeThread *thread) +{ + if (self_aware) + { + emit BecameSelfAware(); + + //restore old peer connections, if any + QList<quint32> c2c_peer_ids; + + c2c_peer_ids = msgqueues.keys(); + + for (int i = 0; i < c2c_peer_ids.size(); i++) + { + int ret; + write_mutex.lock(); + struct PICA_chaninfo *chan = NULL; + + ret = PICA_create_channel(nodelink, c2c_peer_ids[i], &chan); + + qDebug()<<"restoring channel to "<<c2c_peer_ids[i]<<" ret ="<<ret<<"\n"; + write_mutex.unlock(); + } + + //start timer + timer_id = startTimer(10000); + } +} + +void SkyNet::node_status_changed(QString addr, quint16 port, bool alive) +{ + Nodes::NodeRecord nr = {addr, port}; + nodes.UpdateStatus(nr, alive); +} + +void SkyNet::verify_peer_cert(quint32 peer_id, QString cert_pem, bool *verified) +{ +// ViewCertDialog vcd; +// vcd.SetCert(cert_pem); +// vcd.exec(); + Contacts cnt(config_dbname, account_id); + QString stored_cert; + + if ((stored_cert = cnt.GetContactCert(peer_id)).isEmpty()) + { + cnt.SetContactCert(peer_id, cert_pem); + + // -- move to OpenSSL class + QProcess openssl; + + openssl.start("openssl",QStringList()<<"x509"<<"-subject"<<"-nameopt"<<"oneline,-esc_msb,utf8"<<"-noout"); + if (!openssl.waitForStarted()) + return; + + openssl.write(cert_pem.toAscii()); + + if (!openssl.waitForFinished()) + return; + + QByteArray result = openssl.readAll(); + QRegExp rx("CN = [0-9]+\\#([^\\s\\#]+)"); + + + QString name=QString::fromUtf8( result.constData()); + + if (name.contains(rx)) + { + name=rx.cap(1); + } + + // + cnt.SetContactName(peer_id, name); + } + else + {//compare certificates + QString strip_pem[4] = { + "-----BEGIN CERTIFICATE-----", + "-----END CERTIFICATE-----", + "-----BEGIN X509 CERTIFICATE-----", + "-----END X509 CERTIFICATE-----" + }; + QString stripped_stored_cert = stored_cert; + QString stripped_received_cert = cert_pem; + + for (int i = 0; i < 4; i++) + { + stripped_stored_cert.replace(strip_pem[i], ""); + stripped_received_cert.replace(strip_pem[i], ""); + } + + QByteArray stored_DER, received_DER; + + stored_DER = QByteArray::fromBase64(stripped_stored_cert.toAscii().constData()); + received_DER = QByteArray::fromBase64(stripped_received_cert.toAscii().constData()); + + if (stored_DER.size() != received_DER.size() || + memcmp(stored_DER.constData(), received_DER.constData(), stored_DER.size()) != 0) + {//put scary message here + //QMessageBox mbx; + //mbx.setText(tr("Certificate is forged!!!")); + //mbx.exec(); + //ForgedCertDialog fcd(peer_id, cert_pem, stored_cert); + //fcd.exec(); + *verified = false; + emit CertificateForged(peer_id, cert_pem, stored_cert); + } + + + } +} + +void SkyNet::timerEvent(QTimerEvent *e) +{ +if (self_aware) + { + QList<quint32> c2c_peer_ids = msgqueues.keys(); + + write_mutex.lock(); + + c2c_peer_ids = filter_existing_chans(c2c_peer_ids); + + for (int i = 0; i < c2c_peer_ids.size(); i++) + { + int ret; + + struct PICA_chaninfo *chan = NULL; + + ret = PICA_create_channel(nodelink, c2c_peer_ids[i], &chan); + + qDebug()<<"restoring channel to "<<c2c_peer_ids[i]<<" ret ="<<ret<<" in timer event\n"; + + } + write_mutex.unlock(); + } +else + { + killTimer(e->timerId()); + Accounts::AccountRecord acc = this->CurrentAccount(); + Join(acc); + } +} + +void SkyNet::Join(Accounts::AccountRecord &accrec) +{ + QList<Nodes::NodeRecord> nodelist; + skynet_account = accrec; + nodelist = nodes.GetNodes(); + + if (nodelist.count()==0) + { + status = QObject::tr("No known Pica Pica nodes"); + return; + } + + for (int i=0;i<nodelist.count() && !self_aware;i++) + {//TODO FIXME сделать запуск потоков порциями, а не все сразу + threads.append(new NodeThread(nodelist[i],&self_aware,skynet_account,&nodelink, &write_mutex)); + connect(threads.last(), SIGNAL(finished()),this,SLOT(nodethread_finished())); + connect(threads.last(), SIGNAL(NodeStatusChanged(QString,quint16,bool)), this, SLOT(node_status_changed(QString,quint16,bool))); + connect(threads.last(), SIGNAL(ConnectedToNode(QString,quint16,NodeThread*)), this, SLOT(nodethread_connected(QString,quint16,NodeThread*))); + } + +} + +void SkyNet::Exit() +{ + for (int i = 0; i< threads.count(); i++) + threads[i]->CloseThread(); + + self_aware = false; + + killTimer(timer_id); +} + +bool SkyNet::isSelfAware() +{ + return self_aware; +} + +void SkyNet::SendMessage(quint32 to, QString msg) +{ +int ret = PICA_OK; +struct PICA_chaninfo *iptr; + +write_mutex.lock();//<< + +if ( (iptr = find_active_chan(to)) ) +{ + //write_mutex.lock();//<< + ret = PICA_send_msg(iptr, msg.toUtf8().data(), msg.toUtf8().size()); + //write_mutex.unlock();//>> + + if (ret != PICA_OK) + emit UnableToDeliver(to, msg); + + write_mutex.unlock();//>> + return; +} + + + if (msgqueues.contains(to)) + { + msgqueues[to].append(msg); + } + else + { + struct PICA_chaninfo *chan = NULL; + + ret = PICA_create_channel(nodelink, to, &chan); + + QList<QString> l; + l.append(msg); + msgqueues[to] = l; + } + + +write_mutex.unlock();//>> + + if (ret != PICA_OK) + emit UnableToDeliver(to, msg); + +} + +void SkyNet::flush_msgqueue(quint32 to) +{ + struct PICA_chaninfo *chan; + int ret; + + if (self_aware && msgqueues.contains(to)) + { + qDebug()<<"flushing "<<to<<" message queue\n"; + + if ((chan = find_active_chan(to))) + { + while( !msgqueues[to].empty() ) + { + ret = PICA_send_msg(chan, msgqueues[to].first().toUtf8().data(), msgqueues[to].first().toUtf8().size()); + + if (ret != PICA_OK) + { + emit UnableToDeliver(to, msgqueues[to].first()); + break; + } + + msgqueues[to].removeFirst(); + + } + + if (msgqueues[to].empty()) + msgqueues.remove(to); + } + } +} + +struct PICA_chaninfo * SkyNet::find_active_chan(quint32 peer_id) +{ +if (!nodelink) + return NULL; + +struct PICA_chaninfo *iptr = nodelink->chan_list_head; + +while(iptr) + { + if (iptr->state == PICA_CHANSTATE_ACTIVE && iptr->peer_id == peer_id) + break; + + iptr = iptr->next; + } + +return iptr; +} + +QList<quint32> SkyNet::filter_existing_chans(QList<quint32> peer_ids) +{ + QList<quint32> ret = peer_ids; + + if (!nodelink) + return ret; + + struct PICA_chaninfo *iptr = nodelink->chan_list_head; + + while(iptr) + { + if (peer_ids.contains(iptr->peer_id)) + ret.removeOne(iptr->peer_id); + + iptr = iptr->next; + } + + return ret; +} + +void SkyNet::emit_Delivered(quint32 to) +{ +emit Delivered(to); +} + +void SkyNet::emit_MessageReceived(quint32 from, QString msg) +{ +emit MessageReceived(from, msg); +} + +void SkyNet::emit_UnableToDeliver(quint32 to, QString msg) +{ +emit UnableToDeliver(to, msg); +} + +void SkyNet::emit_PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified) +{ + emit PeerCertificateReceived(peer_id, cert_pem, verified); +} + +//callbacks + +//all callbacks are executed in separate thread, created in Nodethread instance, REMEMBER THAT !!! +// write_mutex is locked + +void SkyNet::newmsg_cb(unsigned int peer_id, char *msgbuf, unsigned int nb, int type) +{ +QString msg = QString::fromUtf8(msgbuf, nb); + +skynet->emit_MessageReceived(peer_id, msg); +} + +void SkyNet::msgok_cb(unsigned int peer_id) +{ +skynet->emit_Delivered(peer_id); +} + +void SkyNet::channel_established_cb(unsigned int peer_id) +{ +skynet->flush_msgqueue(peer_id); +} + +void SkyNet::channel_failed(unsigned int peer_id) +{ + qDebug()<<"channel failed ("<<peer_id<<")\n"; +} + +int SkyNet::accept_cb(unsigned int caller_id) +{ +return 1; //implement black list +} + +void SkyNet::notfound_cb(unsigned int callee_id) +{ + qDebug()<<"not found ("<<callee_id<<")\n"; + + /* + if (skynet->msgqueues.contains(callee_id)) + { + while ( ! skynet->msgqueues[callee_id].empty()) + { + skynet->emit_UnableToDeliver(callee_id, skynet->msgqueues[callee_id].last()); + skynet->msgqueues[callee_id].removeLast(); + } + // msgqueues.remove(callee_id) was forgotten + } + */ +} + +void SkyNet::channel_closed_cb(unsigned int peer_id, int reason) +{ + qDebug()<<"channel closed ("<<peer_id<<")\n"; + +} + +void SkyNet::nodelist_cb(int type, void *addr_bin, const char *addr_str, unsigned int port) +{ + Nodes::NodeRecord nr = {addr_str, port}; + skynet->nodes.Add(nr); +} + +int SkyNet::peer_cert_verify_cb(unsigned int peer_id, const char *cert_pem, unsigned int nb) +{ + bool verified = true; + + skynet->emit_PeerCertificateReceived(peer_id, QString::fromAscii(cert_pem, nb), &verified); + + if (!verified) + return 0; + + return 1; +} diff --git a/pica-client/skynet.h b/pica-client/skynet.h new file mode 100644 index 0000000..21d760c --- /dev/null +++ b/pica-client/skynet.h @@ -0,0 +1,83 @@ +#ifndef SKYNET_H +#define SKYNET_H + +#include "nodes.h" +#include "nodethread.h" +#include "../PICA_client.h" +#include "accounts.h" +#include <QObject> +#include <QMap> + + +class SkyNet : public QObject +{ + Q_OBJECT +public: + SkyNet(); + void Join(Accounts::AccountRecord &accrec); + bool isSelfAware(); + QString& Status(); + void Exit(); + + void SendMessage(quint32 to, QString msg); + Accounts::AccountRecord CurrentAccount() {return skynet_account;}; + +signals: + void MessageReceived(quint32 from, QString msg); + void UnableToDeliver(quint32 to, QString msg); + void Delivered(quint32 to); + void BecameSelfAware(); + void LostSelfAwareness(); + void PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified); + void CertificateForged(quint32 peer_id, QString received_cert, QString stored_cert); + +private: + Nodes nodes; + QList<NodeThread*> threads; + bool self_aware; + QString status; + struct PICA_conninfo *nodelink; + Accounts::AccountRecord skynet_account; + QMutex write_mutex; + QMap<quint32, QList<QString> > msgqueues; + int timer_id; + + void timerEvent(QTimerEvent * e); + + void flush_msgqueue(quint32 to); + struct PICA_chaninfo *find_active_chan(quint32 peer_id); + QList<quint32> filter_existing_chans(QList<quint32> peer_ids); + + void emit_MessageReceived(quint32 from, QString msg); + void emit_UnableToDeliver(quint32 to, QString msg); + void emit_Delivered(quint32 to); + void emit_PeerCertificateReceived(quint32 peer_id, QString cert_pem, bool *verified); + + //получение сообщения. + static void newmsg_cb(unsigned int peer_id,char* msgbuf,unsigned int nb,int type); + //получение подтверждения о доставке сообщения + static void msgok_cb(unsigned int peer_id); + //создание канала с собеседником + static void channel_established_cb(unsigned int peer_id); + //создать канал не удалось + static void channel_failed(unsigned int peer_id); + //входящий запрос на создание канала от пользователя с номером caller_id + static int accept_cb(unsigned int caller_id); + //запрошенный пользователь не найден, в оффлайне или отказался от общения + static void notfound_cb(unsigned int callee_id); + + static void channel_closed_cb(unsigned int peer_id, int reason); + + static void nodelist_cb(int type, void *addr_bin, const char *addr_str, unsigned int port); + + static int peer_cert_verify_cb(unsigned int peer_id, const char *cert_pem, unsigned int nb); + +private slots: + void nodethread_finished(); + void nodethread_connected(QString addr, quint16 port, NodeThread *thread); + void node_status_changed(QString addr,quint16 port,bool alive); + void verify_peer_cert(quint32 peer_id, QString cert_pem, bool *verified); + +}; + +#endif // SKYNET_H |
