5dce40b1981a1e09c3126bbc26a360a73e2bf3aa
[libeap.git] / wpa_supplicant / wpa_gui-qt4 / peers.cpp
1 /*
2  * wpa_gui - Peers class
3  * Copyright (c) 2009, Atheros Communications
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 version 2 as
7  * published by the Free Software Foundation.
8  *
9  * Alternatively, this software may be distributed under the terms of BSD
10  * license.
11  *
12  * See README and COPYING for more details.
13  */
14
15 #include <cstdio>
16 #include <QImageReader>
17 #include <QMessageBox>
18
19 #include "wpa_ctrl.h"
20 #include "wpagui.h"
21 #include "stringquery.h"
22 #include "peers.h"
23
24
25 static const int peer_role_address = Qt::UserRole + 1;
26 static const int peer_role_type = Qt::UserRole + 2;
27 static const int peer_role_uuid = Qt::UserRole + 3;
28
29 /*
30  * TODO:
31  * - add current AP info (e.g., from WPS) in station mode
32  * - different icons to indicate peer type
33  */
34
35 enum peer_type {
36         PEER_TYPE_ASSOCIATED_STATION,
37         PEER_TYPE_AP,
38         PEER_TYPE_AP_WPS,
39         PEER_TYPE_WPS_PIN_NEEDED,
40         PEER_TYPE_WPS_ER_AP,
41         PEER_TYPE_WPS_ER_ENROLLEE
42 };
43
44
45 Peers::Peers(QWidget *parent, const char *, bool, Qt::WFlags)
46         : QDialog(parent)
47 {
48         setupUi(this);
49
50         if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
51         {
52                 default_icon = new QIcon(":/icons/wpa_gui.svg");
53                 ap_icon = new QIcon(":/icons/ap.svg");
54                 laptop_icon = new QIcon(":/icons/laptop.svg");
55         } else {
56                 default_icon = new QIcon(":/icons/wpa_gui.png");
57                 ap_icon = new QIcon(":/icons/ap.png");
58                 laptop_icon = new QIcon(":/icons/laptop.png");
59         }
60
61         peers->setModel(&model);
62         peers->setResizeMode(QListView::Adjust);
63
64         peers->setContextMenuPolicy(Qt::CustomContextMenu);
65         connect(peers, SIGNAL(customContextMenuRequested(const QPoint &)),
66                 this, SLOT(context_menu(const QPoint &)));
67
68         wpagui = NULL;
69 }
70
71
72 void Peers::setWpaGui(WpaGui *_wpagui)
73 {
74         wpagui = _wpagui;
75         update_peers();
76 }
77
78
79 Peers::~Peers()
80 {
81         delete default_icon;
82         delete ap_icon;
83         delete laptop_icon;
84 }
85
86
87 void Peers::languageChange()
88 {
89         retranslateUi(this);
90 }
91
92
93 void Peers::context_menu(const QPoint &pos)
94 {
95         QMenu *menu = new QMenu;
96         if (menu == NULL)
97                 return;
98
99         QModelIndex idx = peers->indexAt(pos);
100         if (idx.isValid()) {
101                 ctx_item = model.itemFromIndex(idx);
102                 int type = ctx_item->data(peer_role_type).toInt();
103                 QString title;
104                 switch (type) {
105                 case PEER_TYPE_ASSOCIATED_STATION:
106                         title = tr("Associated station");
107                         break;
108                 case PEER_TYPE_AP:
109                         title = tr("AP");
110                         break;
111                 case PEER_TYPE_AP_WPS:
112                         title = tr("WPS AP");
113                         break;
114                 case PEER_TYPE_WPS_PIN_NEEDED:
115                         title = tr("WPS PIN needed");
116                         break;
117                 case PEER_TYPE_WPS_ER_AP:
118                         title = tr("ER: WPS AP");
119                         break;
120                 case PEER_TYPE_WPS_ER_ENROLLEE:
121                         title = tr("ER: WPS Enrollee");
122                         break;
123                 }
124                 menu->addAction(title)->setEnabled(false);
125                 menu->addSeparator();
126
127                 if (type == PEER_TYPE_ASSOCIATED_STATION ||
128                     type == PEER_TYPE_AP_WPS ||
129                     type == PEER_TYPE_WPS_PIN_NEEDED ||
130                     type == PEER_TYPE_WPS_ER_ENROLLEE) {
131                         /* TODO: only for peers that are requesting WPS PIN
132                          * method */
133                         menu->addAction(QString("Enter WPS PIN"), this,
134                                         SLOT(enter_pin()));
135                 }
136         } else {
137                 ctx_item = NULL;
138                 menu->addAction(QString("Refresh"), this, SLOT(ctx_refresh()));
139         }
140
141         menu->exec(peers->mapToGlobal(pos));
142 }
143
144
145 void Peers::enter_pin()
146 {
147         if (ctx_item == NULL)
148                 return;
149
150         int peer_type = ctx_item->data(peer_role_type).toInt();
151         QString uuid;
152         QString addr;
153         if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE)
154                 uuid = ctx_item->data(peer_role_uuid).toString();
155         else
156                 addr = ctx_item->data(peer_role_address).toString();
157
158         StringQuery input(tr("PIN:"));
159         input.setWindowTitle(tr("PIN for ") + ctx_item->text());
160         if (input.exec() != QDialog::Accepted)
161                 return;
162
163         char cmd[100];
164         char reply[100];
165         size_t reply_len;
166
167         if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
168                 snprintf(cmd, sizeof(cmd), "WPS_ER_PIN %s %s",
169                          uuid.toAscii().constData(),
170                          input.get_string().toAscii().constData());
171         } else {
172                 snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
173                          addr.toAscii().constData(),
174                          input.get_string().toAscii().constData());
175         }
176         reply_len = sizeof(reply) - 1;
177         if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
178                 QMessageBox msg;
179                 msg.setIcon(QMessageBox::Warning);
180                 msg.setText("Failed to set the WPS PIN.");
181                 msg.exec();
182         }
183 }
184
185
186 void Peers::ctx_refresh()
187 {
188         update_peers();
189 }
190
191
192 void Peers::add_station(QString info)
193 {
194         QStringList lines = info.split(QRegExp("\\n"));
195         QString name;
196
197         for (QStringList::Iterator it = lines.begin();
198              it != lines.end(); it++) {
199                 int pos = (*it).indexOf('=') + 1;
200                 if (pos < 1)
201                         continue;
202
203                 if ((*it).startsWith("wpsDeviceName="))
204                         name = (*it).mid(pos);
205         }
206
207         if (name.isEmpty())
208                 name = lines[0];
209
210         QStandardItem *item = new QStandardItem(*laptop_icon, name);
211         if (item) {
212                 item->setData(lines[0], peer_role_address);
213                 item->setData(PEER_TYPE_ASSOCIATED_STATION,
214                               peer_role_type);
215                 item->setToolTip(info);
216                 model.appendRow(item);
217         }
218 }
219
220
221 void Peers::add_stations()
222 {
223         char reply[2048];
224         size_t reply_len;
225         char cmd[30];
226         int res;
227
228         reply_len = sizeof(reply) - 1;
229         if (wpagui->ctrlRequest("STA-FIRST", reply, &reply_len) < 0)
230                 return;
231
232         do {
233                 reply[reply_len] = '\0';
234                 QString info(reply);
235                 char *txt = reply;
236                 while (*txt != '\0' && *txt != '\n')
237                         txt++;
238                 *txt++ = '\0';
239                 if (strncmp(reply, "FAIL", 4) == 0 ||
240                     strncmp(reply, "UNKNOWN", 7) == 0)
241                         break;
242
243                 add_station(info);
244
245                 reply_len = sizeof(reply) - 1;
246                 snprintf(cmd, sizeof(cmd), "STA-NEXT %s", reply);
247                 res = wpagui->ctrlRequest(cmd, reply, &reply_len);
248         } while (res >= 0);
249 }
250
251
252 void Peers::add_single_station(const char *addr)
253 {
254         char reply[2048];
255         size_t reply_len;
256         char cmd[30];
257
258         reply_len = sizeof(reply) - 1;
259         snprintf(cmd, sizeof(cmd), "STA %s", addr);
260         if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
261                 return;
262
263         reply[reply_len] = '\0';
264         QString info(reply);
265         char *txt = reply;
266         while (*txt != '\0' && *txt != '\n')
267                 txt++;
268         *txt++ = '\0';
269         if (strncmp(reply, "FAIL", 4) == 0 ||
270             strncmp(reply, "UNKNOWN", 7) == 0)
271                 return;
272
273         add_station(info);
274 }
275
276
277 void Peers::add_scan_results()
278 {
279         char reply[2048];
280         size_t reply_len;
281         int index;
282         char cmd[20];
283
284         index = 0;
285         while (wpagui) {
286                 snprintf(cmd, sizeof(cmd), "BSS %d", index++);
287                 if (index > 1000)
288                         break;
289
290                 reply_len = sizeof(reply) - 1;
291                 if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0)
292                         break;
293                 reply[reply_len] = '\0';
294
295                 QString bss(reply);
296                 if (bss.isEmpty() || bss.startsWith("FAIL"))
297                         break;
298
299                 QString ssid, bssid, flags, wps_name;
300
301                 QStringList lines = bss.split(QRegExp("\\n"));
302                 for (QStringList::Iterator it = lines.begin();
303                      it != lines.end(); it++) {
304                         int pos = (*it).indexOf('=') + 1;
305                         if (pos < 1)
306                                 continue;
307
308                         if ((*it).startsWith("bssid="))
309                                 bssid = (*it).mid(pos);
310                         else if ((*it).startsWith("flags="))
311                                 flags = (*it).mid(pos);
312                         else if ((*it).startsWith("ssid="))
313                                 ssid = (*it).mid(pos);
314                         else if ((*it).startsWith("wps_device_name="))
315                                 wps_name = (*it).mid(pos);
316                 }
317
318                 QString name = wps_name;
319                 if (name.isEmpty())
320                         name = ssid + "\n" + bssid;
321
322                 QStandardItem *item = new QStandardItem(*ap_icon, name);
323                 if (item) {
324                         item->setData(bssid, peer_role_address);
325                         if (flags.contains("[WPS"))
326                                 item->setData(PEER_TYPE_AP_WPS,
327                                               peer_role_type);
328                         else
329                                 item->setData(PEER_TYPE_AP, peer_role_type);
330
331                         for (int i = 0; i < lines.size(); i++) {
332                                 if (lines[i].length() > 60) {
333                                         lines[i].remove(
334                                                 60, lines[i].length());
335                                         lines[i] += "..";
336                                 }
337                         }
338                         item->setToolTip(lines.join("\n"));
339                         model.appendRow(item);
340                 }
341         }
342 }
343
344
345 void Peers::update_peers()
346 {
347         model.clear();
348         if (wpagui == NULL)
349                 return;
350
351         char reply[20];
352         size_t replylen = sizeof(reply) - 1;
353         wpagui->ctrlRequest("WPS_ER_START", reply, &replylen);
354
355         add_stations();
356         add_scan_results();
357 }
358
359
360 QStandardItem * Peers::find_addr(QString addr)
361 {
362         if (model.rowCount() == 0)
363                 return NULL;
364
365         QModelIndexList lst = model.match(model.index(0, 0), peer_role_address,
366                                           addr);
367         if (lst.size() == 0)
368                 return NULL;
369         return model.itemFromIndex(lst[0]);
370 }
371
372
373 QStandardItem * Peers::find_uuid(QString uuid)
374 {
375         if (model.rowCount() == 0)
376                 return NULL;
377
378         QModelIndexList lst = model.match(model.index(0, 0), peer_role_uuid,
379                                           uuid);
380         if (lst.size() == 0)
381                 return NULL;
382         return model.itemFromIndex(lst[0]);
383 }
384
385
386 void Peers::event_notify(WpaMsg msg)
387 {
388         QString text = msg.getMsg();
389
390         if (text.startsWith(WPS_EVENT_PIN_NEEDED)) {
391                 /*
392                  * WPS-PIN-NEEDED 5a02a5fa-9199-5e7c-bc46-e183d3cb32f7
393                  * 02:2a:c4:18:5b:f3
394                  * [Wireless Client|Company|cmodel|123|12345|1-0050F204-1]
395                  */
396                 QStringList items = text.split(' ');
397                 QString uuid = items[1];
398                 QString addr = items[2];
399                 QString name = "";
400
401                 QStandardItem *item = find_addr(addr);
402                 if (item)
403                         return;
404
405                 int pos = text.indexOf('[');
406                 if (pos >= 0) {
407                         int pos2 = text.lastIndexOf(']');
408                         if (pos2 >= pos) {
409                                 items = text.mid(pos + 1, pos2 - pos - 1).
410                                         split('|');
411                                 name = items[0];
412                                 items.append(addr);
413                         }
414                 }
415
416                 item = new QStandardItem(*laptop_icon, name);
417                 if (item) {
418                         item->setData(addr, peer_role_address);
419                         item->setData(PEER_TYPE_WPS_PIN_NEEDED,
420                                       peer_role_type);
421                         item->setToolTip(items.join(QString("\n")));
422                         model.appendRow(item);
423                 }
424                 return;
425         }
426
427         if (text.startsWith(AP_STA_CONNECTED)) {
428                 /* AP-STA-CONNECTED 02:2a:c4:18:5b:f3 */
429                 QStringList items = text.split(' ');
430                 QString addr = items[1];
431                 QStandardItem *item = find_addr(addr);
432                 if (item == NULL || item->data(peer_role_type).toInt() !=
433                     PEER_TYPE_ASSOCIATED_STATION)
434                         add_single_station(addr.toAscii().constData());
435                 return;
436         }
437
438         if (text.startsWith(AP_STA_DISCONNECTED)) {
439                 /* AP-STA-DISCONNECTED 02:2a:c4:18:5b:f3 */
440                 QStringList items = text.split(' ');
441                 QString addr = items[1];
442
443                 if (model.rowCount() == 0)
444                         return;
445
446                 QModelIndexList lst = model.match(model.index(0, 0),
447                                                   peer_role_address, addr);
448                 for (int i = 0; i < lst.size(); i++) {
449                         QStandardItem *item = model.itemFromIndex(lst[i]);
450                         if (item && item->data(peer_role_type).toInt() ==
451                             PEER_TYPE_ASSOCIATED_STATION)
452                                 model.removeRow(lst[i].row());
453                 }
454                 return;
455         }
456
457         if (text.startsWith(WPS_EVENT_ER_AP_ADD)) {
458                 /*
459                  * WPS-ER-AP-ADD 87654321-9abc-def0-1234-56789abc0002|
460                  * Very friendly name|Company|Long description of the model|
461                  * WAP|http://w1.fi/|http://w1.fi/hostapd/
462                  */
463                 int pos = text.indexOf(' ');
464                 if (pos < 0)
465                         return;
466                 QStringList items = text.mid(pos + 1).split('|');
467                 if (items.size() < 2)
468                         return;
469
470                 QStandardItem *item = find_uuid(items[0]);
471                 if (item)
472                         return;
473
474                 item = new QStandardItem(*ap_icon, items[1]);
475                 if (item) {
476                         item->setData(items[0], peer_role_uuid);
477                         item->setData(PEER_TYPE_WPS_ER_AP, peer_role_type);
478                         item->setToolTip(items.join(QString("\n")));
479                         model.appendRow(item);
480                 }
481
482                 return;
483         }
484
485         if (text.startsWith(WPS_EVENT_ER_AP_REMOVE)) {
486                 /* WPS-ER-AP-REMOVE 87654321-9abc-def0-1234-56789abc0002 */
487                 QStringList items = text.split(' ');
488                 if (items.size() < 2)
489                         return;
490                 if (model.rowCount() == 0)
491                         return;
492
493                 QModelIndexList lst = model.match(model.index(0, 0),
494                                                   peer_role_uuid, items[1]);
495                 for (int i = 0; i < lst.size(); i++) {
496                         QStandardItem *item = model.itemFromIndex(lst[i]);
497                         if (item && item->data(peer_role_type).toInt() ==
498                             PEER_TYPE_WPS_ER_AP)
499                                 model.removeRow(lst[i].row());
500                 }
501                 return;
502         }
503
504         if (text.startsWith(WPS_EVENT_ER_ENROLLEE_ADD)) {
505                 /*
506                  * WPS-ER-ENROLLEE-ADD 2b7093f1-d6fb-5108-adbb-bea66bb87333
507                  * 02:66:a0:ee:17:27 M1=1 config_methods=0x14d dev_passwd_id=0
508                  * pri_dev_type=1-0050F204-1
509                  * |Wireless Client|Company|cmodel|123|12345|
510                  */
511                 QStringList items = text.split(' ');
512                 if (items.size() < 3)
513                         return;
514                 QString uuid = items[1];
515                 QString addr = items[2];
516
517                 int pos = text.indexOf('|');
518                 if (pos < 0)
519                         return;
520                 items = text.mid(pos + 1).split('|');
521                 if (items.size() < 1)
522                         return;
523                 QString name = items[0];
524                 if (name.length() == 0)
525                         name = addr;
526
527                 remove_enrollee_uuid(uuid);
528
529                 QStandardItem *item;
530                 item = new QStandardItem(*laptop_icon, name);
531                 if (item) {
532                         item->setData(uuid, peer_role_uuid);
533                         item->setData(addr, peer_role_address);
534                         item->setData(PEER_TYPE_WPS_ER_ENROLLEE,
535                                       peer_role_type);
536                         item->setToolTip(items.join(QString("\n")));
537                         model.appendRow(item);
538                 }
539
540                 return;
541         }
542
543         if (text.startsWith(WPS_EVENT_ER_ENROLLEE_REMOVE)) {
544                 /*
545                  * WPS-ER-ENROLLEE-REMOVE 2b7093f1-d6fb-5108-adbb-bea66bb87333
546                  * 02:66:a0:ee:17:27
547                  */
548                 QStringList items = text.split(' ');
549                 if (items.size() < 2)
550                         return;
551                 remove_enrollee_uuid(items[1]);
552                 return;
553         }
554 }
555
556
557 void Peers::closeEvent(QCloseEvent *)
558 {
559         if (wpagui) {
560                 char reply[20];
561                 size_t replylen = sizeof(reply) - 1;
562                 wpagui->ctrlRequest("WPS_ER_STOP", reply, &replylen);
563         }
564 }
565
566
567 void Peers::done(int r)
568 {
569         QDialog::done(r);
570         close();
571 }
572
573
574 void Peers::remove_enrollee_uuid(QString uuid)
575 {
576         if (model.rowCount() == 0)
577                 return;
578
579         QModelIndexList lst = model.match(model.index(0, 0),
580                                           peer_role_uuid, uuid);
581         for (int i = 0; i < lst.size(); i++) {
582                 QStandardItem *item = model.itemFromIndex(lst[i]);
583                 if (item && item->data(peer_role_type).toInt() ==
584                     PEER_TYPE_WPS_ER_ENROLLEE)
585                         model.removeRow(lst[i].row());
586         }
587 }