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