mainwindow.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. /*
  2. * ZeroTier One - Global Peer to Peer Ethernet
  3. * Copyright (C) 2012-2013 ZeroTier Networks LLC
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. *
  18. * --
  19. *
  20. * ZeroTier may be used and distributed under the terms of the GPLv3, which
  21. * are available at: http://www.gnu.org/licenses/gpl-3.0.html
  22. *
  23. * If you would like to embed ZeroTier into a commercial application or
  24. * redistribute it in a modified binary form, please contact ZeroTier Networks
  25. * LLC. Start here: http://www.zerotier.com/
  26. */
  27. #include "mainwindow.h"
  28. #include "aboutwindow.h"
  29. #include "networkwidget.h"
  30. #include "ui_mainwindow.h"
  31. #include "installdialog.h"
  32. #include <string>
  33. #include <map>
  34. #include <set>
  35. #include <vector>
  36. #include <stdexcept>
  37. #include <utility>
  38. #include <QClipboard>
  39. #include <QMutex>
  40. #include <QCoreApplication>
  41. #include <QDir>
  42. #include <QFile>
  43. #include <QMessageBox>
  44. #include <QDebug>
  45. #include <QProcess>
  46. #include <QStringList>
  47. #include <QVBoxLayout>
  48. #include <QScrollBar>
  49. #include <QEventLoop>
  50. QNetworkAccessManager *nam;
  51. // Globally visible
  52. ZeroTier::Node::LocalClient *zeroTierClient = (ZeroTier::Node::LocalClient *)0;
  53. // Main window instance for app
  54. static MainWindow *mainWindow = (MainWindow *)0;
  55. static void handleZTMessage(void *arg,unsigned long id,const char *line)
  56. {
  57. static std::map< unsigned long,std::vector<std::string> > ztReplies;
  58. static QMutex ztReplies_m;
  59. ztReplies_m.lock();
  60. if (*line) {
  61. ztReplies[id].push_back(std::string(line));
  62. ztReplies_m.unlock();
  63. } else { // empty lines conclude transmissions
  64. std::map< unsigned long,std::vector<std::string> >::iterator r(ztReplies.find(id));
  65. if (r != ztReplies.end()) {
  66. MainWindow::ZTMessageEvent *event = new MainWindow::ZTMessageEvent(r->second);
  67. ztReplies.erase(r);
  68. ztReplies_m.unlock();
  69. QCoreApplication::postEvent(mainWindow,event); // must post since this may be another thread
  70. } else ztReplies_m.unlock();
  71. }
  72. }
  73. MainWindow::MainWindow(QWidget *parent) :
  74. QMainWindow(parent),
  75. ui(new Ui::MainWindow),
  76. pollServiceTimerId(0)
  77. {
  78. ui->setupUi(this);
  79. #ifdef __APPLE__
  80. if (!QFile::exists("/Library/Application Support/ZeroTier/One/zerotier-one")) {
  81. // If the service is not installed, download the installer and run it
  82. // for the first time.
  83. this->setEnabled(false);
  84. InstallDialog *id = new InstallDialog(this);
  85. id->setModal(true);
  86. id->show();
  87. this->setHidden(true);
  88. return;
  89. }
  90. #endif
  91. this->pollServiceTimerId = this->startTimer(1000);
  92. this->setEnabled(false); // gets enabled when updates are received
  93. mainWindow = this;
  94. this->cyclesSinceResponseFromService = 0;
  95. if (ui->networkListWidget->verticalScrollBar())
  96. ui->networkListWidget->verticalScrollBar()->setSingleStep(8);
  97. QWidgetList widgets = this->findChildren<QWidget*>();
  98. foreach(QWidget* widget, widgets)
  99. widget->setAttribute(Qt::WA_MacShowFocusRect,false);
  100. }
  101. MainWindow::~MainWindow()
  102. {
  103. delete ui;
  104. delete zeroTierClient;
  105. zeroTierClient = (ZeroTier::Node::LocalClient *)0;
  106. mainWindow = (MainWindow *)0;
  107. }
  108. void MainWindow::timerEvent(QTimerEvent *event)
  109. {
  110. event->accept();
  111. if (this->isHidden())
  112. return;
  113. if (!zeroTierClient) {
  114. std::string authToken;
  115. if (!ZeroTier::Utils::readFile(ZeroTier::Node::LocalClient::authTokenDefaultUserPath().c_str(),authToken)) {
  116. #ifdef __APPLE__
  117. if (QFile::exists("/Library/Application Support/ZeroTier/One/zerotier-one")) {
  118. // Run the little AppleScript hack that asks for admin credentials and
  119. // then installs the auth token file in the current user's home.
  120. QMessageBox::information(this,"Authorization Required","You must authenticate to authorize this user to\nadministrate ZeroTier One on this computer.\n\n(This only needs to be done once.)",QMessageBox::Ok,QMessageBox::NoButton);
  121. QString authHelperPath(QCoreApplication::applicationDirPath() + "/../Resources/helpers/mac/ZeroTier One (Authenticate).app/Contents/MacOS/applet");
  122. if (!QFile::exists(authHelperPath)) {
  123. QMessageBox::critical(this,"Unable to Locate Helper","Unable to locate authorization helper, cannot obtain authentication token.",QMessageBox::Ok,QMessageBox::NoButton);
  124. QApplication::exit(1);
  125. return;
  126. }
  127. QProcess::execute(authHelperPath,QStringList());
  128. } else {
  129. // If the service is not installed, download the installer and run it
  130. // for the first time.
  131. this->setEnabled(false);
  132. InstallDialog *id = new InstallDialog(this);
  133. id->setModal(true);
  134. id->show();
  135. this->setHidden(true);
  136. return;
  137. }
  138. #endif
  139. if (!ZeroTier::Utils::readFile(ZeroTier::Node::LocalClient::authTokenDefaultUserPath().c_str(),authToken)) {
  140. QMessageBox::critical(this,"Cannot Authorize","Unable to authorize this user to administrate ZeroTier One.\n\nTo do so manually, copy 'authtoken.secret' from the ZeroTier One home directory to '.zeroTierOneAuthToken' in your home directory and set file modes on this file to only be readable by you (e.g. 0600 on Mac or Linux systems).",QMessageBox::Ok,QMessageBox::NoButton);
  141. QApplication::exit(1);
  142. return;
  143. }
  144. }
  145. zeroTierClient = new ZeroTier::Node::LocalClient(authToken.c_str(),0,&handleZTMessage,this);
  146. }
  147. // TODO: do something more user-friendly here... or maybe try to restart
  148. // the service?
  149. if (++this->cyclesSinceResponseFromService == 4)
  150. QMessageBox::critical(this,"No Response from Service","The ZeroTier One service does not appear to be running.",QMessageBox::Ok,QMessageBox::NoButton);
  151. zeroTierClient->send("info");
  152. zeroTierClient->send("listnetworks");
  153. zeroTierClient->send("listpeers");
  154. }
  155. void MainWindow::customEvent(QEvent *event)
  156. {
  157. ZTMessageEvent *m = (ZTMessageEvent *)event; // only one custom event type so far
  158. if (m->ztMessage.size() == 0)
  159. return;
  160. std::vector<std::string> hdr(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[0]));
  161. if (hdr.size() < 2)
  162. return;
  163. if (hdr[0] != "200")
  164. return;
  165. this->cyclesSinceResponseFromService = 0;
  166. if (hdr[1] == "info") {
  167. if (hdr.size() >= 3)
  168. this->myAddress = hdr[2].c_str();
  169. if (hdr.size() >= 4)
  170. this->myStatus = hdr[3].c_str();
  171. if (hdr.size() >= 5)
  172. this->myVersion = hdr[4].c_str();
  173. } else if (hdr[1] == "listnetworks") {
  174. std::map< std::string,std::vector<std::string> > newNetworks;
  175. for(unsigned long i=1;i<m->ztMessage.size();++i) {
  176. std::vector<std::string> l(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[i]));
  177. // 200 listnetworks <nwid> <name> <status> <config age> <type> <dev> <ips>
  178. if ((l.size() == 9)&&(l[2].length() == 16))
  179. newNetworks[l[2]] = l;
  180. }
  181. if (newNetworks != networks) {
  182. networks = newNetworks;
  183. for (bool removed=true;removed;) {
  184. removed = false;
  185. for(int r=0;r<ui->networkListWidget->count();++r) {
  186. NetworkWidget *nw = (NetworkWidget *)ui->networkListWidget->itemWidget(ui->networkListWidget->item(r));
  187. if (!networks.count(nw->networkId())) {
  188. ui->networkListWidget->setVisible(false); // HACK to prevent an occasional crash here, discovered through hours of shotgun debugging... :P
  189. delete ui->networkListWidget->takeItem(r);
  190. removed = true;
  191. break;
  192. }
  193. }
  194. }
  195. ui->networkListWidget->setVisible(true);
  196. std::set<std::string> alreadyDisplayed;
  197. for(int r=0;r<ui->networkListWidget->count();++r) {
  198. NetworkWidget *nw = (NetworkWidget *)ui->networkListWidget->itemWidget(ui->networkListWidget->item(r));
  199. if (networks.count(nw->networkId()) > 0) {
  200. alreadyDisplayed.insert(nw->networkId());
  201. std::vector<std::string> &l = networks[nw->networkId()];
  202. nw->setNetworkName(l[3]);
  203. nw->setStatus(l[4],l[5]);
  204. nw->setNetworkType(l[6]);
  205. nw->setNetworkDeviceName(l[7]);
  206. nw->setIps(l[8]);
  207. }
  208. }
  209. for(std::map< std::string,std::vector<std::string> >::iterator nwdata(networks.begin());nwdata!=networks.end();++nwdata) {
  210. if (alreadyDisplayed.count(nwdata->first) == 0) {
  211. std::vector<std::string> &l = nwdata->second;
  212. NetworkWidget *nw = new NetworkWidget((QWidget *)0,nwdata->first);
  213. nw->setNetworkName(l[3]);
  214. nw->setStatus(l[4],l[5]);
  215. nw->setNetworkType(l[6]);
  216. nw->setNetworkDeviceName(l[7]);
  217. nw->setIps(l[8]);
  218. QListWidgetItem *item = new QListWidgetItem();
  219. item->setSizeHint(nw->sizeHint());
  220. ui->networkListWidget->addItem(item);
  221. ui->networkListWidget->setItemWidget(item,nw);
  222. }
  223. }
  224. }
  225. } else if (hdr[1] == "listpeers") {
  226. this->numPeers = 0;
  227. for(unsigned long i=1;i<m->ztMessage.size();++i) {
  228. std::vector<std::string> l(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[i]));
  229. if ((l.size() >= 5)&&((l[3] != "-")||(l[4] != "-")))
  230. ++this->numPeers; // number of direct peers online -- check for active IPv4 and/or IPv6 address
  231. }
  232. }
  233. if (this->myAddress.size())
  234. ui->addressButton->setText(this->myAddress);
  235. else ui->addressButton->setText(" ");
  236. QString st(this->myStatus);
  237. st += ", v";
  238. st += this->myVersion;
  239. st += ", ";
  240. st += QString::number(this->numPeers);
  241. st += " direct links to peers";
  242. ui->statusLabel->setText(st);
  243. if (this->myStatus == "ONLINE") {
  244. if (!this->isEnabled())
  245. this->setEnabled(true);
  246. } else {
  247. if (this->isEnabled())
  248. this->setEnabled(false);
  249. }
  250. }
  251. void MainWindow::on_joinNetworkButton_clicked()
  252. {
  253. QString toJoin(ui->networkIdLineEdit->text());
  254. ui->networkIdLineEdit->setText(QString());
  255. if (!zeroTierClient) // sanity check
  256. return;
  257. if (toJoin.size() != 16) {
  258. QMessageBox::information(this,"Invalid Network ID","The network ID you entered was not valid. Enter a 16-digit hexadecimal network ID, like '8056c2e21c000001'.",QMessageBox::Ok,QMessageBox::NoButton);
  259. return;
  260. }
  261. zeroTierClient->send((QString("join ") + toJoin).toStdString());
  262. }
  263. void MainWindow::on_actionAbout_triggered()
  264. {
  265. AboutWindow *about = new AboutWindow(this);
  266. about->show();
  267. }
  268. void MainWindow::on_networkIdLineEdit_textChanged(const QString &text)
  269. {
  270. QString newText;
  271. for(QString::const_iterator i(text.begin());i!=text.end();++i) {
  272. switch(i->toLatin1()) {
  273. case '0': newText.append('0'); break;
  274. case '1': newText.append('1'); break;
  275. case '2': newText.append('2'); break;
  276. case '3': newText.append('3'); break;
  277. case '4': newText.append('4'); break;
  278. case '5': newText.append('5'); break;
  279. case '6': newText.append('6'); break;
  280. case '7': newText.append('7'); break;
  281. case '8': newText.append('8'); break;
  282. case '9': newText.append('9'); break;
  283. case 'a': newText.append('a'); break;
  284. case 'b': newText.append('b'); break;
  285. case 'c': newText.append('c'); break;
  286. case 'd': newText.append('d'); break;
  287. case 'e': newText.append('e'); break;
  288. case 'f': newText.append('f'); break;
  289. case 'A': newText.append('a'); break;
  290. case 'B': newText.append('b'); break;
  291. case 'C': newText.append('c'); break;
  292. case 'D': newText.append('d'); break;
  293. case 'E': newText.append('e'); break;
  294. case 'F': newText.append('f'); break;
  295. default: break;
  296. }
  297. }
  298. if (newText.size() > 16)
  299. newText.truncate(16);
  300. ui->networkIdLineEdit->setText(newText);
  301. }
  302. void MainWindow::on_addressButton_clicked()
  303. {
  304. QApplication::clipboard()->setText(this->myAddress);
  305. }