/* * ZeroTier One - Global Peer to Peer Ethernet * Copyright (C) 2012-2013 ZeroTier Networks LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * -- * * ZeroTier may be used and distributed under the terms of the GPLv3, which * are available at: http://www.gnu.org/licenses/gpl-3.0.html * * If you would like to embed ZeroTier into a commercial application or * redistribute it in a modified binary form, please contact ZeroTier Networks * LLC. Start here: http://www.zerotier.com/ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "mainwindow.h" #include "aboutwindow.h" #include "networkwidget.h" #include "ui_mainwindow.h" #ifdef __APPLE__ #include #include #include #include #include #include "mac_doprivileged.h" #endif // Globally visible ZeroTier::Node::LocalClient *zeroTierClient = (ZeroTier::Node::LocalClient *)0; // Main window instance for app static MainWindow *mainWindow = (MainWindow *)0; // Handles message from ZeroTier One service static void handleZTMessage(void *arg,unsigned long id,const char *line) { static std::map< unsigned long,std::vector > ztReplies; static QMutex ztReplies_m; ztReplies_m.lock(); if (*line) { ztReplies[id].push_back(std::string(line)); ztReplies_m.unlock(); } else { // empty lines conclude transmissions std::map< unsigned long,std::vector >::iterator r(ztReplies.find(id)); if (r != ztReplies.end()) { // The message is packed into an event and sent to the main window where // the actual parsing code lives. MainWindow::ZTMessageEvent *event = new MainWindow::ZTMessageEvent(r->second); ztReplies.erase(r); ztReplies_m.unlock(); QCoreApplication::postEvent(mainWindow,event); // must post since this may be another thread } else ztReplies_m.unlock(); } } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), pollServiceTimerId(-1) { mainWindow = this; ui->setupUi(this); if (ui->networkListWidget->verticalScrollBar()) ui->networkListWidget->verticalScrollBar()->setSingleStep(8); #ifdef __APPLE__ QWidgetList widgets = this->findChildren(); foreach(QWidget *widget, widgets) widget->setAttribute(Qt::WA_MacShowFocusRect,false); #endif ui->noNetworksLabel->setVisible(true); ui->noNetworksLabel->setText("Connecting to Service..."); ui->bottomContainerWidget->setVisible(false); ui->networkListWidget->setVisible(false); this->pollServiceTimerId = this->startTimer(1000); this->cyclesSinceResponseFromService = 0; } MainWindow::~MainWindow() { delete ui; delete zeroTierClient; zeroTierClient = (ZeroTier::Node::LocalClient *)0; mainWindow = (MainWindow *)0; } void MainWindow::timerEvent(QTimerEvent *event) { event->accept(); if (this->isHidden()) return; if (pollServiceTimerId < 0) return; if (!zeroTierClient) { std::string authToken; if (!ZeroTier::Utils::readFile(ZeroTier::Node::LocalClient::authTokenDefaultUserPath().c_str(),authToken)) { #ifdef __APPLE__ if (QFile::exists("/Library/Application Support/ZeroTier/One/zerotier-one")) { // Authorize user by copying auth token into local home directory QMessageBox::information(this,"Authorization Needed","Administrator privileges are required to allow the current user to control ZeroTier One on this computer. (You only have to do this once.)",QMessageBox::Ok,QMessageBox::NoButton); std::string homePath(QDir::homePath().toStdString()); QString zt1Caches(QDir::homePath() + "/Library/Caches/ZeroTier/One"); QDir::root().mkpath(zt1Caches); std::string tmpPath((zt1Caches + "/auth.sh").toStdString()); FILE *scr = fopen(tmpPath.c_str(),"w"); if (!scr) { QMessageBox::critical(this,"Cannot Authorize","Unable to authorize this user to administrate ZeroTier One. (Cannot write to temporary Library/Caches/ZeroTier/One folder.)",QMessageBox::Ok,QMessageBox::NoButton); QApplication::exit(1); return; } fprintf(scr,"#!/bin/bash\n"); fprintf(scr,"export PATH=\"/bin:/usr/bin:/sbin:/usr/sbin\"\n"); fprintf(scr,"if [ -f '/Library/Application Support/ZeroTier/One/authtoken.secret' ]; then\n"); fprintf(scr," mkdir -p '%s/Library/Application Support/ZeroTier/One'\n",homePath.c_str()); fprintf(scr," chown %d '%s/Library/Application Support/ZeroTier'\n",(int)getuid(),homePath.c_str()); fprintf(scr," chgrp %d '%s/Library/Application Support/ZeroTier'\n",(int)getgid(),homePath.c_str()); fprintf(scr," chmod 0700 '%s/Library/Application Support/ZeroTier'\n",homePath.c_str()); fprintf(scr," chown %d '%s/Library/Application Support/ZeroTier/One'\n",(int)getuid(),homePath.c_str()); fprintf(scr," chgrp %d '%s/Library/Application Support/ZeroTier/One'\n",(int)getgid(),homePath.c_str()); fprintf(scr," chmod 0700 '%s/Library/Application Support/ZeroTier/One'\n",homePath.c_str()); fprintf(scr," cp -f '/Library/Application Support/ZeroTier/One/authtoken.secret' '%s/Library/Application Support/ZeroTier/One/authtoken.secret'\n",homePath.c_str()); fprintf(scr," chown %d '%s/Library/Application Support/ZeroTier/One/authtoken.secret'\n",(int)getuid(),homePath.c_str()); fprintf(scr," chgrp %d '%s/Library/Application Support/ZeroTier/One/authtoken.secret'\n",(int)getgid(),homePath.c_str()); fprintf(scr," chmod 0600 '%s/Library/Application Support/ZeroTier/One/authtoken.secret'\n",homePath.c_str()); fprintf(scr,"fi\n"); fprintf(scr,"exit 0\n"); fclose(scr); chmod(tmpPath.c_str(),0755); macExecutePrivilegedShellCommand((std::string("'")+tmpPath+"' >>/dev/null 2>&1").c_str()); unlink(tmpPath.c_str()); } #endif if (!ZeroTier::Utils::readFile(ZeroTier::Node::LocalClient::authTokenDefaultUserPath().c_str(),authToken)) { if (!ZeroTier::Utils::readFile(ZeroTier::Node::LocalClient::authTokenDefaultSystemPath().c_str(),authToken)) { QMessageBox::critical(this,"Cannot Authorize","Unable to authorize this user to administrate ZeroTier One. (Did you enter your password correctly?)",QMessageBox::Ok,QMessageBox::NoButton); QApplication::exit(1); return; } } } zeroTierClient = new ZeroTier::Node::LocalClient(authToken.c_str(),0,&handleZTMessage,this); } if (++this->cyclesSinceResponseFromService >= 3) { if (this->cyclesSinceResponseFromService == 3) QMessageBox::warning(this,"Service Not Running","Can't connect to the ZeroTier One service. Is it running?",QMessageBox::Ok); ui->noNetworksLabel->setVisible(true); ui->noNetworksLabel->setText("Connecting to Service..."); ui->bottomContainerWidget->setVisible(false); ui->networkListWidget->setVisible(false); } zeroTierClient->send("info"); zeroTierClient->send("listnetworks"); zeroTierClient->send("listpeers"); } void MainWindow::customEvent(QEvent *event) { QMessageBox::information(this,"event","event",QMessageBox::Ok); ZTMessageEvent *m = (ZTMessageEvent *)event; // only one custom event type so far if (m->ztMessage.size() == 0) return; this->cyclesSinceResponseFromService = 0; std::vector hdr(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[0])); if (hdr.size() < 2) return; if (hdr[0] != "200") return; if (hdr[1] == "info") { if (hdr.size() >= 3) this->myAddress = hdr[2].c_str(); if (hdr.size() >= 4) this->myStatus = hdr[3].c_str(); if (hdr.size() >= 5) this->myVersion = hdr[4].c_str(); } else if (hdr[1] == "listnetworks") { std::map< std::string,std::vector > newNetworks; for(unsigned long i=1;iztMessage.size();++i) { std::vector l(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[i])); // 200 listnetworks if ((l.size() == 9)&&(l[2].length() == 16)) newNetworks[l[2]] = l; } if (newNetworks != networks) { networks = newNetworks; for (bool removed=true;removed;) { removed = false; for(int r=0;rnetworkListWidget->count();++r) { NetworkWidget *nw = (NetworkWidget *)ui->networkListWidget->itemWidget(ui->networkListWidget->item(r)); if (!networks.count(nw->networkId())) { ui->networkListWidget->setVisible(false); // HACK to prevent an occasional crash here, discovered through hours of shotgun debugging... :P delete ui->networkListWidget->takeItem(r); removed = true; break; } } } ui->networkListWidget->setVisible(true); std::set alreadyDisplayed; for(int r=0;rnetworkListWidget->count();++r) { NetworkWidget *nw = (NetworkWidget *)ui->networkListWidget->itemWidget(ui->networkListWidget->item(r)); if (networks.count(nw->networkId()) > 0) { alreadyDisplayed.insert(nw->networkId()); std::vector &l = networks[nw->networkId()]; nw->setNetworkName(l[3]); nw->setStatus(l[4],l[5]); nw->setNetworkType(l[6]); nw->setNetworkDeviceName(l[7]); nw->setIps(l[8]); } } for(std::map< std::string,std::vector >::iterator nwdata(networks.begin());nwdata!=networks.end();++nwdata) { if (alreadyDisplayed.count(nwdata->first) == 0) { std::vector &l = nwdata->second; NetworkWidget *nw = new NetworkWidget((QWidget *)0,nwdata->first); nw->setNetworkName(l[3]); nw->setStatus(l[4],l[5]); nw->setNetworkType(l[6]); nw->setNetworkDeviceName(l[7]); nw->setIps(l[8]); QListWidgetItem *item = new QListWidgetItem(); item->setSizeHint(nw->sizeHint()); ui->networkListWidget->addItem(item); ui->networkListWidget->setItemWidget(item,nw); } } } } else if (hdr[1] == "listpeers") { this->numPeers = 0; for(unsigned long i=1;iztMessage.size();++i) { std::vector l(ZeroTier::Node::LocalClient::splitLine(m->ztMessage[i])); if ((l.size() >= 5)&&((l[3] != "-")||(l[4] != "-"))) ++this->numPeers; // number of direct peers online -- check for active IPv4 and/or IPv6 address } } if (!ui->networkListWidget->count()) { ui->noNetworksLabel->setText("You Have Not Joined Any Networks"); ui->noNetworksLabel->setVisible(true); } else ui->noNetworksLabel->setVisible(false); if (!ui->bottomContainerWidget->isVisible()) ui->bottomContainerWidget->setVisible(true); if (!ui->networkListWidget->isVisible()) ui->networkListWidget->setVisible(true); if (this->myAddress.size()) ui->addressButton->setText(this->myAddress); else ui->addressButton->setText(" "); QString st(this->myStatus); st += ", v"; st += this->myVersion; st += ", "; st += QString::number(this->numPeers); st += " direct links to peers"; ui->statusLabel->setText(st); } void MainWindow::on_joinNetworkButton_clicked() { QString toJoin(ui->networkIdLineEdit->text()); ui->networkIdLineEdit->setText(QString()); if (!zeroTierClient) // sanity check return; if (toJoin.size() != 16) { 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); return; } zeroTierClient->send((QString("join ") + toJoin).toStdString()); } void MainWindow::on_actionAbout_triggered() { AboutWindow *about = new AboutWindow(this); about->show(); } void MainWindow::on_networkIdLineEdit_textChanged(const QString &text) { QString newText; for(QString::const_iterator i(text.begin());i!=text.end();++i) { switch(i->toLatin1()) { case '0': newText.append('0'); break; case '1': newText.append('1'); break; case '2': newText.append('2'); break; case '3': newText.append('3'); break; case '4': newText.append('4'); break; case '5': newText.append('5'); break; case '6': newText.append('6'); break; case '7': newText.append('7'); break; case '8': newText.append('8'); break; case '9': newText.append('9'); break; case 'a': newText.append('a'); break; case 'b': newText.append('b'); break; case 'c': newText.append('c'); break; case 'd': newText.append('d'); break; case 'e': newText.append('e'); break; case 'f': newText.append('f'); break; case 'A': newText.append('a'); break; case 'B': newText.append('b'); break; case 'C': newText.append('c'); break; case 'D': newText.append('d'); break; case 'E': newText.append('e'); break; case 'F': newText.append('f'); break; default: break; } } if (newText.size() > 16) newText.truncate(16); ui->networkIdLineEdit->setText(newText); } void MainWindow::on_addressButton_clicked() { QApplication::clipboard()->setText(this->myAddress); }