794754d7adce2e5603cf4f52a245ab3644b285a4
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-preferences.cpp
1 /*
2  * dhcpcd-qt
3  * Copyright 2014-2015 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 #include <QDialog>
28 #include <QCheckBox>
29 #include <QComboBox>
30 #include <QFormLayout>
31 #include <QFrame>
32 #include <QGridLayout>
33 #include <QHBoxLayout>
34 #include <QIcon>
35 #include <QLabel>
36 #include <QLineEdit>
37 #include <QMessageBox>
38 #include <QPixmap>
39 #include <QPushButton>
40 #include <QStandardItemModel>
41 #include <QVBoxLayout>
42
43 #include <cerrno>
44
45 #include "config.h"
46 #include "dhcpcd-preferences.h"
47 #include "dhcpcd-ipv4validator.h"
48 #include "dhcpcd-qt.h"
49 #include "dhcpcd-wi.h"
50
51 DhcpcdPreferences::DhcpcdPreferences(DhcpcdQt *parent)
52     : QDialog(parent)
53 {
54         this->parent = parent;
55         resize(400, 200);
56         setWindowIcon(DhcpcdQt::getIcon("status", "network-transmit-receive"));
57         setWindowTitle(tr("Network Preferences"));
58         QPoint p = QCursor::pos();
59         move(p.x(), p.y());
60
61         name = NULL;
62         config = NULL;
63         configIndex = -1;
64         eWhat = eBlock = NULL;
65         iface = NULL;
66
67         QVBoxLayout *layout = new QVBoxLayout();
68
69         QGridLayout *topLayout = new QGridLayout();
70         QLabel *conf = new QLabel(tr("Configure:"));
71         topLayout->addWidget(conf, 0, 0);
72
73         what = new QComboBox();
74         connect(what, SIGNAL(currentIndexChanged(const QString &)),
75             this, SLOT(listBlocks(const QString &)));
76         topLayout->addWidget(what, 0, 1);
77         blocks = new QComboBox();
78         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
79             this, SLOT(showBlock(const QString &)));
80         topLayout->addWidget(blocks, 0, 2);
81
82         topLayout->setColumnStretch(2, 10);
83
84         QWidget *topBox = new QWidget();
85         topBox->setLayout(topLayout);
86         layout->addWidget(topBox);
87         QFrame *topSep = new QFrame();
88         topSep->setFrameShape(QFrame::HLine);
89         topSep->setFrameShadow(QFrame::Sunken);
90         layout->addWidget(topSep);
91
92         autoConf = new QCheckBox(tr("Automatically configure empty options"));
93         autoConf->setChecked(true);
94         layout->addWidget(autoConf);
95
96         DhcpcdIPv4Validator *v =
97             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Plain, this);
98         DhcpcdIPv4Validator *vc =
99             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::CIDR, this);
100         DhcpcdIPv4Validator *vs =
101             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Spaced, this);
102         ip = new QLineEdit();
103         ip->setValidator(vc);
104         router = new QLineEdit();
105         router->setValidator(v);
106         rdnss = new QLineEdit();
107         rdnss->setValidator(vs);
108         dnssl = new QLineEdit();
109 #if defined(__NetBSD__) || (__OpenBSD__)
110         dnssl->setMaxLength(1024);
111 #else
112         dnssl->setMaxLength(256);
113 #endif
114         QFormLayout *ipLayout = new QFormLayout();
115         ipLayout->addRow(tr("IP Address:"), ip);
116         ipLayout->addRow(tr("Router:"), router);
117         ipLayout->addRow(tr("DNS Servers:"), rdnss);
118         ipLayout->addRow(tr("DNS Search:"), dnssl);
119         ipSetup = new QWidget();
120         ipSetup->setLayout(ipLayout);
121         layout->addWidget(ipSetup);
122
123         QHBoxLayout *buttonLayout = new QHBoxLayout();
124         clear = new QPushButton(tr("&Clear"));
125         clear->setIcon(QIcon::fromTheme("edit-clear"));
126         buttonLayout->addWidget(clear);
127         QPushButton *rebind = new QPushButton(tr("&Rebind"));
128         rebind->setIcon(QIcon::fromTheme("application-x-executable"));
129         buttonLayout->addWidget(rebind);
130         QPushButton *close = new QPushButton(tr("&Close"));
131         close->setIcon(QIcon::fromTheme("window-close"));
132         buttonLayout->addWidget(close);
133         QWidget *buttons = new QWidget();
134         buttons->setLayout(buttonLayout);
135         layout->addWidget(buttons);
136
137         QIcon wired = DhcpcdQt::getIcon("devices", "network-wired");
138         what->addItem(wired, tr("interface"));
139         QIcon wireless = DhcpcdQt::getIcon("devices", "network-wireless");
140         what->addItem(wireless, tr("SSID"));
141
142         connect(clear, SIGNAL(clicked()), this, SLOT(clearConfig()));
143         connect(rebind, SIGNAL(clicked()), this, SLOT(rebind()));
144         connect(close, SIGNAL(clicked()), this, SLOT(tryClose()));
145
146         setLayout(layout);
147
148         autoConf->setEnabled(false);
149         ipSetup->setEnabled(false);
150         clear->setEnabled(false);
151
152         DHCPCD_CONNECTION *con = parent->getConnection();
153         if (!dhcpcd_config_writeable(con))
154                 QMessageBox::warning(this, tr("Not writeable"),
155                     tr("The dhcpcd configuration file is not writeable\n\n%1")
156                     .arg(dhcpcd_cffile(con)));
157 }
158
159 DhcpcdPreferences::~DhcpcdPreferences()
160 {
161
162         free(eWhat);
163         eWhat = NULL;
164         free(eBlock);
165         eBlock = NULL;
166 }
167
168 void DhcpcdPreferences::closeEvent(QCloseEvent *e)
169 {
170
171         parent->dialogClosed(this);
172         QDialog::closeEvent(e);
173 }
174
175 void DhcpcdPreferences::listBlocks(const QString &txt)
176 {
177         char **list, **lp;
178         QIcon icon;
179
180         /* clear and then disconnect so we trigger a save */
181         blocks->clear();
182         blocks->disconnect(this);
183
184         free(eWhat);
185         eWhat = strdup(txt.toLower().toAscii());
186
187         list = dhcpcd_config_blocks(parent->getConnection(),
188             txt.toLower().toAscii());
189
190         if (txt == "interface") {
191                 char **ifaces, **i;
192
193                 blocks->addItem(tr("Select an interface"));
194                 ifaces = dhcpcd_interface_names_sorted(parent->getConnection());
195                 for (i = ifaces; i && *i; i++) {
196                         for (lp = list; lp && *lp; lp++) {
197                                 if (strcmp(*i, *lp) == 0)
198                                         break;
199                         }
200                         icon = DhcpcdQt::getIcon("actions",
201                             lp && *lp ?
202                             "document-save" : "document-new");
203                         blocks->addItem(icon, *i);
204                 }
205                 dhcpcd_freev(ifaces);
206         } else {
207                 QList<DhcpcdWi *> *wis = parent->getWis();
208
209                 blocks->addItem(tr("Select a SSID"));
210                 for (int i = 0; i < wis->size(); i++) {
211                         DHCPCD_WI_SCAN *scan;
212                         DhcpcdWi *wi = wis->at(i);
213
214                         for (scan = wi->getScans(); scan; scan = scan->next) {
215                                 for (lp = list; lp && *lp; lp++) {
216                                         if (strcmp(scan->ssid, *lp) == 0)
217                                                 break;
218                                 }
219                                 icon = DhcpcdQt::getIcon("actions",
220                                     lp && *lp ?
221                                     "document-save" : "document-new");
222                                 blocks->addItem(icon, scan->ssid);
223                         }
224                 }
225         }
226
227         dhcpcd_freev(list);
228
229         /* Now make the 1st item unselectable and reconnect */
230         qobject_cast<QStandardItemModel *>
231             (blocks->model())->item(0)->setEnabled(false);
232         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
233             this, SLOT(showBlock(const QString &)));
234
235 }
236
237 void DhcpcdPreferences::clearConfig()
238 {
239         QIcon icon = DhcpcdQt::getIcon("actions", "document-new");
240
241         blocks->setItemIcon(blocks->currentIndex(), icon);
242         autoConf->setChecked(true);
243         ip->setText("");
244         router->setText("");
245         rdnss->setText("");
246         dnssl->setText("");
247 }
248
249 void DhcpcdPreferences::showConfig()
250 {
251         const char *val;
252         bool a;
253
254         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
255                 a = false;
256         else
257                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
258                     (iface && iface->flags & IFF_POINTOPOINT));
259         autoConf->setChecked(a);
260         ip->setText(val);
261         router->setText(dhcpcd_config_get_static(config, "routers="));
262         rdnss->setText(dhcpcd_config_get_static(config,"domain_name_servers="));
263         dnssl->setText(dhcpcd_config_get_static(config, "domain_search="));
264 }
265
266 bool DhcpcdPreferences::changedConfig()
267 {
268         const char *val;
269         bool a;
270
271         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
272                 a = false;
273         else
274                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
275                     (iface && iface->flags & IFF_POINTOPOINT));
276         if (autoConf->isChecked() != a)
277                 return true;
278         if (ip->text().compare(val))
279                 return true;
280         val = dhcpcd_config_get_static(config, "routers=");
281         if (router->text().compare(val))
282                 return true;
283         val = dhcpcd_config_get_static(config, "domain_name_servers=");
284         if (rdnss->text().compare(val))
285                 return true;
286         val = dhcpcd_config_get_static(config, "domain_search=");
287         if (rdnss->text().compare(val))
288                 return true;
289         return false;
290 }
291
292
293 const char *DhcpcdPreferences::getString(QLineEdit *le)
294 {
295         if (le->text().isEmpty())
296                 return NULL;
297         return le->text().trimmed().toAscii();
298 }
299
300 bool DhcpcdPreferences::setOption(const char *opt, const char *val, bool *ret)
301 {
302         if (opt[strlen(opt) - 1] == '=') {
303                 if (!dhcpcd_config_set_static(&config, opt, val))
304                         qCritical("dhcpcd_config_set_static: %s",
305                             strerror(errno));
306                 else
307                         return true;
308         } else {
309                 if (!dhcpcd_config_set(&config, opt, val))
310                         qCritical("dhcpcd_config_set: %s",
311                             strerror(errno));
312                 else
313                         return true;
314         }
315
316         if (ret)
317                 *ret = false;
318         return false;
319 }
320
321
322 bool DhcpcdPreferences::makeConfig()
323 {
324         const char ns[] = "", *val;
325         bool a, ret;
326
327         a = autoConf->isChecked();
328         ret = true;
329         if (iface && iface->flags & IFF_POINTOPOINT)
330                 setOption("ip_address=", a ? NULL : ns, &ret);
331         else {
332                 val = getString(ip);
333                 setOption("inform", a ? val : NULL, &ret);
334                 setOption("ip_address=", a ? NULL : val, &ret);
335         }
336
337         val = getString(router);
338         setOption("routers=", val, &ret);
339
340         val = getString(rdnss);
341         setOption("domain_name_servers=", val, &ret);
342
343         val = getString(dnssl);
344         setOption("domain_search=", val, &ret);
345
346         return ret;
347 }
348
349 bool DhcpcdPreferences::writeConfig(bool *cancel)
350 {
351
352         switch (QMessageBox::question(this, tr("Save Configuration"),
353             tr("Do you want to save your changes?"),
354             QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel)) {
355         case QMessageBox::Cancel:
356                 *cancel = true;
357                 return false;
358         case QMessageBox::Discard:
359                 *cancel = false;
360                 return true;
361         default:
362                 break;
363         }
364         *cancel = false;
365
366         DHCPCD_CONNECTION *con = parent->getConnection();
367         if (!makeConfig()) {
368                 qCritical("failed to make config");
369                 goto err;
370         }
371         if (!dhcpcd_config_write(con, eWhat, eBlock, config)) {
372                 qCritical("dhcpcd_config_write: %s", strerror(errno));
373                 QMessageBox::critical(parent,
374                     tr("Failed to write configuration"),
375                     tr("Failed to write configuration:\n\n%1: %2")
376                     .arg(dhcpcd_cffile(con))
377                     .arg(strerror(errno)));
378                 goto err;
379         }
380
381         /* Braces to avoid jump error */
382         {
383                 QIcon icon = DhcpcdQt::getIcon("actions", "document-save");
384                 blocks->setItemIcon(configIndex, icon);
385         }
386         return true;
387
388 err:
389         /* Reload our config if there is a problem */
390         config = dhcpcd_config_read(con, eWhat, eBlock);
391         return false;
392 }
393
394 void DhcpcdPreferences::showBlock(const QString &txt)
395 {
396
397         if (eBlock) {
398                 if (changedConfig()) {
399                         bool cancel;
400                         if (!writeConfig(&cancel))
401                                 return;
402                 }
403                 free(eBlock);
404         }
405         if (txt.isEmpty())
406                 eBlock = NULL;
407         else
408                 eBlock = strdup(txt.toAscii());
409
410         dhcpcd_config_free(config);
411         iface = NULL;
412         DHCPCD_CONNECTION *con = parent->getConnection();
413
414         if (eBlock && eWhat) {
415                 if (strcmp(eWhat, "interface") == 0)
416                         iface = dhcpcd_get_if(con, eBlock, "link");
417                 ip->setEnabled(iface == NULL ||
418                     !(iface->flags & IFF_POINTOPOINT));
419                 errno = 0;
420                 config = dhcpcd_config_read(con, eWhat, eBlock);
421                 if (config == NULL && errno) {
422                         const char *s;
423
424                         s = strerror(errno);
425                         qCritical("dhcpcd_config_read: %s", s);
426                         QMessageBox::critical(this,
427                             tr("Error reading configuration"),
428                             tr("Error reading: ") + dhcpcd_cffile(con) +
429                             "\n\n" + s);
430                 }
431         } else
432                 config = NULL;
433
434         if (config == NULL)
435                 configIndex = -1;
436         else
437                 configIndex = blocks->currentIndex();
438
439         showConfig();
440         bool enabled = dhcpcd_config_writeable(con) && eBlock != NULL;
441         autoConf->setEnabled(enabled);
442         ipSetup->setEnabled(enabled);
443         clear->setEnabled(enabled);
444 }
445
446 bool DhcpcdPreferences::tryRebind(const char *ifname)
447 {
448
449         if (dhcpcd_rebind(parent->getConnection(), ifname) == 0)
450                 return true;
451
452         qCritical("dhcpcd_rebind: %s", strerror(errno));
453         QMessageBox::critical(this,
454             tr("Rebind failed"),
455             ifname ? tr("Failed to rebind interface %1: %2")
456             .arg(ifname).arg(strerror(errno)) :
457             tr("Failed to rebind: %1")
458             .arg(strerror(errno)));
459         return false;
460 }
461
462 void DhcpcdPreferences::rebind()
463 {
464
465         if (changedConfig()) {
466                 bool cancel;
467                 writeConfig(&cancel);
468                 if (cancel)
469                         return;
470         }
471
472         DHCPCD_CONNECTION *con = parent->getConnection();
473         DHCPCD_IF *i;
474         bool worked;
475         bool found;
476         if (eBlock == NULL || strcmp(eWhat, "interface") == 0) {
477                 worked = tryRebind(iface ? iface->ifname : NULL);
478                 goto done;
479         }
480
481         found = false;
482         worked = true;
483         for (i = dhcpcd_interfaces(con); i; i = i->next) {
484                 if (strcmp(i->type, "link") == 0 &&
485                     (i->ssid && strcmp(i->ssid, eBlock) == 0))
486                 {
487                         found = true;
488                         if (!tryRebind(i->ifname))
489                                 worked = false;
490                 }
491         }
492         if (!found) {
493                 QMessageBox::information(this,
494                     tr("No matching interface"),
495                     tr("No interface is bound to this SSID to rebind"));
496                 return;
497         }
498
499 done:
500         if (worked)
501                 close();
502 }
503
504 void DhcpcdPreferences::tryClose()
505 {
506
507         if (changedConfig()) {
508                 bool cancel;
509                 writeConfig(&cancel);
510                 if (cancel)
511                         return;
512         }
513         close();
514 }