032f10b379c1bcd23adedd6ec8bca568098d77e6
[dhcpcd-ui] / src / dhcpcd-qt / dhcpcd-preferences.cpp
1 /*
2  * dhcpcd-qt
3  * Copyright 2014 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         eWhat = eBlock = NULL;
64         iface = NULL;
65
66         QVBoxLayout *layout = new QVBoxLayout();
67
68         QGridLayout *topLayout = new QGridLayout();
69         QLabel *conf = new QLabel(tr("Configure:"));
70         topLayout->addWidget(conf, 0, 0);
71
72         what = new QComboBox();
73         connect(what, SIGNAL(currentIndexChanged(const QString &)),
74             this, SLOT(listBlocks(const QString &)));
75         topLayout->addWidget(what, 0, 1);
76         blocks = new QComboBox();
77         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
78             this, SLOT(showBlock(const QString &)));
79         topLayout->addWidget(blocks, 0, 2);
80
81         topLayout->setColumnStretch(2, 10);
82
83         QWidget *topBox = new QWidget();
84         topBox->setLayout(topLayout);
85         layout->addWidget(topBox);
86         QFrame *topSep = new QFrame();
87         topSep->setFrameShape(QFrame::HLine);
88         topSep->setFrameShadow(QFrame::Sunken);
89         layout->addWidget(topSep);
90
91         autoConf = new QCheckBox(tr("Automatically configure empty options"));
92         autoConf->setChecked(true);
93         layout->addWidget(autoConf);
94
95         DhcpcdIPv4Validator *v =
96             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Plain, this);
97         DhcpcdIPv4Validator *vc =
98             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::CIDR, this);
99         DhcpcdIPv4Validator *vs =
100             new DhcpcdIPv4Validator(DhcpcdIPv4Validator::Spaced, this);
101         ip = new QLineEdit();
102         ip->setValidator(vc);
103         router = new QLineEdit();
104         router->setValidator(v);
105         rdnss = new QLineEdit();
106         rdnss->setValidator(vs);
107         dnssl = new QLineEdit();
108 #if defined(__NetBSD__) || (__OpenBSD__)
109         dnssl->setMaxLength(1024);
110 #else
111         dnssl->setMaxLength(256);
112 #endif
113         QFormLayout *ipLayout = new QFormLayout();
114         ipLayout->addRow(tr("IP Address:"), ip);
115         ipLayout->addRow(tr("Router:"), router);
116         ipLayout->addRow(tr("DNS Servers:"), rdnss);
117         ipLayout->addRow(tr("DNS Search:"), dnssl);
118         ipSetup = new QWidget();
119         ipSetup->setLayout(ipLayout);
120         layout->addWidget(ipSetup);
121
122         QHBoxLayout *buttonLayout = new QHBoxLayout();
123         clear = new QPushButton(tr("&Clear"));
124         clear->setIcon(QIcon::fromTheme("edit-clear"));
125         buttonLayout->addWidget(clear);
126         QPushButton *rebind = new QPushButton(tr("&Rebind"));
127         rebind->setIcon(QIcon::fromTheme("application-x-executable"));
128         buttonLayout->addWidget(rebind);
129         QPushButton *close = new QPushButton(tr("&Close"));
130         close->setIcon(QIcon::fromTheme("window-close"));
131         buttonLayout->addWidget(close);
132         QWidget *buttons = new QWidget();
133         buttons->setLayout(buttonLayout);
134         layout->addWidget(buttons);
135
136         QIcon wired = DhcpcdQt::getIcon("devices", "network-wired");
137         what->addItem(wired, tr("interface"));
138         QIcon wireless = DhcpcdQt::getIcon("devices", "network-wireless");
139         what->addItem(wireless, tr("SSID"));
140
141         connect(clear, SIGNAL(clicked()), this, SLOT(clearConfig()));
142         connect(rebind, SIGNAL(clicked()), this, SLOT(rebind()));
143         connect(close, SIGNAL(clicked()), this, SLOT(tryClose()));
144
145         setLayout(layout);
146
147         autoConf->setEnabled(false);
148         ipSetup->setEnabled(false);
149         clear->setEnabled(false);
150
151         DHCPCD_CONNECTION *con = parent->getConnection();
152         if (!dhcpcd_config_writeable(con))
153                 QMessageBox::warning(this, tr("Not writeable"),
154                     tr("The dhcpcd configuration file is not writeable\n\n%1")
155                     .arg(dhcpcd_cffile(con)));
156 }
157
158 DhcpcdPreferences::~DhcpcdPreferences()
159 {
160
161         free(eWhat);
162         eWhat = NULL;
163         free(eBlock);
164         eBlock = NULL;
165 }
166
167 void DhcpcdPreferences::closeEvent(QCloseEvent *e)
168 {
169
170         parent->dialogClosed(this);
171         QDialog::closeEvent(e);
172 }
173
174 void DhcpcdPreferences::listBlocks(const QString &txt)
175 {
176         char **list, **lp;
177         QIcon icon;
178
179         /* clear and then disconnect so we trigger a save */
180         blocks->clear();
181         blocks->disconnect(this);
182
183         free(eWhat);
184         eWhat = strdup(txt.toLower().toAscii());
185
186         list = dhcpcd_config_blocks(parent->getConnection(),
187             txt.toLower().toAscii());
188
189         if (txt == "interface") {
190                 char **ifaces, **i;
191
192                 blocks->addItem(tr("Select an interface"));
193                 ifaces = dhcpcd_interface_names_sorted(parent->getConnection());
194                 for (i = ifaces; i && *i; i++) {
195                         for (lp = list; lp && *lp; lp++) {
196                                 if (strcmp(*i, *lp) == 0)
197                                         break;
198                         }
199                         icon = DhcpcdQt::getIcon("actions",
200                             lp && *lp ?
201                             "document-save" : "document-new");
202                         blocks->addItem(icon, *i);
203                 }
204                 dhcpcd_freev(ifaces);
205         } else {
206                 QList<DhcpcdWi *> *wis = parent->getWis();
207
208                 blocks->addItem(tr("Select a SSID"));
209                 for (int i = 0; i < wis->size(); i++) {
210                         DHCPCD_WI_SCAN *scan;
211                         DhcpcdWi *wi = wis->at(i);
212
213                         for (scan = wi->getScans(); scan; scan = scan->next) {
214                                 for (lp = list; lp && *lp; lp++) {
215                                         if (strcmp(scan->ssid, *lp) == 0)
216                                                 break;
217                                 }
218                                 icon = DhcpcdQt::getIcon("actions",
219                                     lp && *lp ?
220                                     "document-save" : "document-new");
221                                 blocks->addItem(icon, scan->ssid);
222                         }
223                 }
224         }
225
226         dhcpcd_freev(list);
227
228         /* Now make the 1st item unselectable and reconnect */
229         qobject_cast<QStandardItemModel *>
230             (blocks->model())->item(0)->setEnabled(false);
231         connect(blocks, SIGNAL(currentIndexChanged(const QString &)),
232             this, SLOT(showBlock(const QString &)));
233
234 }
235
236 void DhcpcdPreferences::clearConfig()
237 {
238
239         autoConf->setChecked(true);
240         ip->setText("");
241         router->setText("");
242         rdnss->setText("");
243         dnssl->setText("");
244 }
245
246 void DhcpcdPreferences::showConfig()
247 {
248         const char *val;
249         bool a;
250
251         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
252                 a = false;
253         else
254                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
255                     (iface && iface->flags & IFF_POINTOPOINT));
256         autoConf->setChecked(a);
257         ip->setText(val);
258         router->setText(dhcpcd_config_get_static(config, "routers="));
259         rdnss->setText(dhcpcd_config_get_static(config,"domain_name_servers="));
260         dnssl->setText(dhcpcd_config_get_static(config, "domain_search="));
261 }
262
263 bool DhcpcdPreferences::changedConfig()
264 {
265         const char *val;
266         bool a;
267
268         if ((val = dhcpcd_config_get_static(config, "ip_address=")) != NULL)
269                 a = false;
270         else
271                 a = !((val = dhcpcd_config_get(config, "inform")) == NULL &&
272                     (iface && iface->flags & IFF_POINTOPOINT));
273         if (autoConf->isChecked() != a)
274                 return true;
275         if (ip->text().compare(val))
276                 return true;
277         val = dhcpcd_config_get_static(config, "routers=");
278         if (router->text().compare(val))
279                 return true;
280         val = dhcpcd_config_get_static(config, "domain_name_servers=");
281         if (rdnss->text().compare(val))
282                 return true;
283         val = dhcpcd_config_get_static(config, "domain_search=");
284         if (rdnss->text().compare(val))
285                 return true;
286         return false;
287 }
288
289
290 const char *DhcpcdPreferences::getString(QLineEdit *le)
291 {
292         if (le->text().isEmpty())
293                 return NULL;
294         return le->text().trimmed().toAscii();
295 }
296
297 bool DhcpcdPreferences::setOption(const char *opt, const char *val, bool *ret)
298 {
299         if (opt[strlen(opt) - 1] == '=') {
300                 if (!dhcpcd_config_set_static(&config, opt, val))
301                         qCritical("dhcpcd_config_set_static: %s",
302                             strerror(errno));
303                 else
304                         return true;
305         } else {
306                 if (!dhcpcd_config_set(&config, opt, val))
307                         qCritical("dhcpcd_config_set: %s",
308                             strerror(errno));
309                 else
310                         return true;
311         }
312
313         if (ret)
314                 *ret = false;
315         return false;
316 }
317
318
319 bool DhcpcdPreferences::makeConfig()
320 {
321         const char ns[] = "", *val;
322         bool a, ret;
323
324         a = autoConf->isChecked();
325         ret = true;
326         if (iface && iface->flags & IFF_POINTOPOINT)
327                 setOption("ip_address=", a ? NULL : ns, &ret);
328         else {
329                 val = getString(ip);
330                 setOption("inform", a ? val : NULL, &ret);
331                 setOption("ip_address=", a ? NULL : val, &ret);
332         }
333
334         val = getString(router);
335         setOption("routers=", val, &ret);
336
337         val = getString(rdnss);
338         setOption("domain_name_servers=", val, &ret);
339
340         val = getString(dnssl);
341         setOption("domain_search=", val, &ret);
342
343         return ret;
344 }
345
346 bool DhcpcdPreferences::writeConfig(bool *cancel)
347 {
348
349         switch (QMessageBox::question(this, tr("Save Configuration"),
350             tr("Do you want to save your changes?"),
351             QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel)) {
352         case QMessageBox::Cancel:
353                 *cancel = true;
354                 return false;
355         case QMessageBox::Discard:
356                 *cancel = true;
357                 return true;
358         default:
359                 break;
360         }
361         *cancel = false;
362
363         DHCPCD_CONNECTION *con = parent->getConnection();
364         if (!makeConfig()) {
365                 qCritical("failed to make config");
366                 goto err;
367         }
368         if (!dhcpcd_config_write(con, eWhat, eBlock, config)) {
369                 qCritical("dhcpcd_config_write: %s", strerror(errno));
370                 QMessageBox::critical(parent,
371                     tr("Failed to write configuration"),
372                     tr("Failed to write configuration:\n\n%1: %2")
373                     .arg(dhcpcd_cffile(con))
374                     .arg(strerror(errno)));
375                 goto err;
376         }
377         return true;
378
379 err:
380         /* Reload our config if there is a problem */
381         config = dhcpcd_config_read(con, eWhat, eBlock);
382         return false;
383 }
384
385 void DhcpcdPreferences::showBlock(const QString &txt)
386 {
387
388         if (eBlock) {
389                 if (changedConfig()) {
390                         bool cancel;
391                         if (!writeConfig(&cancel))
392                                 return;
393                 }
394                 free(eBlock);
395         }
396         if (txt.isEmpty())
397                 eBlock = NULL;
398         else
399                 eBlock = strdup(txt.toAscii());
400
401         dhcpcd_config_free(config);
402         iface = NULL;
403         DHCPCD_CONNECTION *con = parent->getConnection();
404
405         if (eBlock && eWhat) {
406                 if (strcmp(eWhat, "interface") == 0)
407                         iface = dhcpcd_get_if(con, eBlock, "link");
408                 ip->setEnabled(iface == NULL ||
409                     !(iface->flags & IFF_POINTOPOINT));
410                 errno = 0;
411                 config = dhcpcd_config_read(con, eWhat, eBlock);
412                 if (config == NULL && errno) {
413                         const char *s;
414
415                         s = strerror(errno);
416                         qCritical("dhcpcd_config_read: %s", s);
417                         QMessageBox::critical(this,
418                             tr("Error reading configuration"),
419                             tr("Error reading: ") + dhcpcd_cffile(con) +
420                             "\n\n" + s);
421                 }
422         } else
423                 config = NULL;
424
425         showConfig();
426         bool enabled = dhcpcd_config_writeable(con) && eBlock != NULL;
427         autoConf->setEnabled(enabled);
428         ipSetup->setEnabled(enabled);
429         clear->setEnabled(enabled);
430 }
431
432 bool DhcpcdPreferences::tryRebind(const char *ifname)
433 {
434
435         if (dhcpcd_rebind(parent->getConnection(), ifname) == 0)
436                 return true;
437
438         qCritical("dhcpcd_rebind: %s", strerror(errno));
439         QMessageBox::critical(this,
440             tr("Rebind failed"),
441             ifname ? tr("Failed to rebind interface %1: %2")
442             .arg(ifname).arg(strerror(errno)) :
443             tr("Failed to rebind: %1")
444             .arg(strerror(errno)));
445         return false;
446 }
447
448 void DhcpcdPreferences::rebind()
449 {
450
451         if (changedConfig()) {
452                 bool cancel;
453                 writeConfig(&cancel);
454                 if (cancel)
455                         return;
456         }
457
458         DHCPCD_CONNECTION *con = parent->getConnection();
459         DHCPCD_IF *i;
460         bool worked;
461         bool found;
462         if (eBlock == NULL || strcmp(eWhat, "interface") == 0) {
463                 worked = tryRebind(iface ? iface->ifname : NULL);
464                 goto done;
465         }
466
467         found = false;
468         worked = true;
469         for (i = dhcpcd_interfaces(con); i; i = i->next) {
470                 if (strcmp(i->type, "link") == 0 &&
471                     (i->ssid && strcmp(i->ssid, eBlock) == 0))
472                 {
473                         found = true;
474                         if (!tryRebind(i->ifname))
475                                 worked = false;
476                 }
477         }
478         if (!found) {
479                 QMessageBox::information(this,
480                     tr("No matching interface"),
481                     tr("No interface is bound to this SSID to rebind"));
482                 return;
483         }
484
485 done:
486         if (worked)
487                 close();
488 }
489
490 void DhcpcdPreferences::tryClose()
491 {
492
493         if (changedConfig()) {
494                 bool cancel;
495                 writeConfig(&cancel);
496                 if (cancel)
497                         return;
498         }
499         close();
500 }