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