wpa_gui: Add peer dialog option for WPS PBC
[libeap.git] / wpa_supplicant / wpa_gui-qt4 / peers.cpp
index 21122f2..165fbad 100644 (file)
 #include "peers.h"
 
 
-static const int peer_role_address = Qt::UserRole + 1;
-static const int peer_role_type = Qt::UserRole + 2;
+enum {
+       peer_role_address = Qt::UserRole + 1,
+       peer_role_type,
+       peer_role_uuid,
+       peer_role_details,
+       peer_role_pri_dev_type,
+       peer_role_ssid,
+       peer_role_config_methods,
+       peer_role_dev_passwd_id
+};
 
 /*
  * TODO:
  * - add current AP info (e.g., from WPS) in station mode
- * - different icons to indicate peer type
  */
 
 enum peer_type {
@@ -36,6 +43,9 @@ enum peer_type {
        PEER_TYPE_AP,
        PEER_TYPE_AP_WPS,
        PEER_TYPE_WPS_PIN_NEEDED,
+       PEER_TYPE_WPS_ER_AP,
+       PEER_TYPE_WPS_ER_AP_UNCONFIGURED,
+       PEER_TYPE_WPS_ER_ENROLLEE
 };
 
 
@@ -45,9 +55,15 @@ Peers::Peers(QWidget *parent, const char *, bool, Qt::WFlags)
        setupUi(this);
 
        if (QImageReader::supportedImageFormats().contains(QByteArray("svg")))
+       {
                default_icon = new QIcon(":/icons/wpa_gui.svg");
-       else
+               ap_icon = new QIcon(":/icons/ap.svg");
+               laptop_icon = new QIcon(":/icons/laptop.svg");
+       } else {
                default_icon = new QIcon(":/icons/wpa_gui.png");
+               ap_icon = new QIcon(":/icons/ap.png");
+               laptop_icon = new QIcon(":/icons/laptop.png");
+       }
 
        peers->setModel(&model);
        peers->setResizeMode(QListView::Adjust);
@@ -70,6 +86,8 @@ void Peers::setWpaGui(WpaGui *_wpagui)
 Peers::~Peers()
 {
        delete default_icon;
+       delete ap_icon;
+       delete laptop_icon;
 }
 
 
@@ -79,6 +97,36 @@ void Peers::languageChange()
 }
 
 
+QString Peers::ItemType(int type)
+{
+       QString title;
+       switch (type) {
+       case PEER_TYPE_ASSOCIATED_STATION:
+               title = tr("Associated station");
+               break;
+       case PEER_TYPE_AP:
+               title = tr("AP");
+               break;
+       case PEER_TYPE_AP_WPS:
+               title = tr("WPS AP");
+               break;
+       case PEER_TYPE_WPS_PIN_NEEDED:
+               title = tr("WPS PIN needed");
+               break;
+       case PEER_TYPE_WPS_ER_AP:
+               title = tr("ER: WPS AP");
+               break;
+       case PEER_TYPE_WPS_ER_AP_UNCONFIGURED:
+               title = tr("ER: WPS AP (Unconfigured)");
+               break;
+       case PEER_TYPE_WPS_ER_ENROLLEE:
+               title = tr("ER: WPS Enrollee");
+               break;
+       }
+       return title;
+}
+
+
 void Peers::context_menu(const QPoint &pos)
 {
        QMenu *menu = new QMenu;
@@ -89,32 +137,36 @@ void Peers::context_menu(const QPoint &pos)
        if (idx.isValid()) {
                ctx_item = model.itemFromIndex(idx);
                int type = ctx_item->data(peer_role_type).toInt();
-               QString title;
-               switch (type) {
-               case PEER_TYPE_ASSOCIATED_STATION:
-                       title = tr("Associated station");
-                       break;
-               case PEER_TYPE_AP:
-                       title = tr("AP");
-                       break;
-               case PEER_TYPE_AP_WPS:
-                       title = tr("WPS AP");
-                       break;
-               case PEER_TYPE_WPS_PIN_NEEDED:
-                       title = tr("WPS PIN needed");
-                       break;
-               }
-               menu->addAction(title)->setEnabled(false);
+               menu->addAction(Peers::ItemType(type))->setEnabled(false);
                menu->addSeparator();
 
-               if (type == PEER_TYPE_ASSOCIATED_STATION ||
-                   type == PEER_TYPE_AP_WPS ||
-                   type == PEER_TYPE_WPS_PIN_NEEDED) {
-                       /* TODO: only for peers that are requesting WPS PIN
-                        * method */
-                       menu->addAction(QString("Enter WPS PIN"), this,
+               int config_methods = -1;
+               QVariant var = ctx_item->data(peer_role_config_methods);
+               if (var.isValid())
+                       config_methods = var.toInt();
+
+               if ((type == PEER_TYPE_ASSOCIATED_STATION ||
+                    type == PEER_TYPE_AP_WPS ||
+                    type == PEER_TYPE_WPS_PIN_NEEDED ||
+                    type == PEER_TYPE_WPS_ER_ENROLLEE) &&
+                   (config_methods == -1 || (config_methods & 0x010c))) {
+                       menu->addAction(tr("Enter WPS PIN"), this,
                                        SLOT(enter_pin()));
                }
+
+               if (type == PEER_TYPE_AP_WPS) {
+                       menu->addAction(tr("Connect (PBC)"), this,
+                                       SLOT(connect_pbc()));
+               }
+
+               if ((type == PEER_TYPE_ASSOCIATED_STATION ||
+                    type == PEER_TYPE_WPS_ER_ENROLLEE) &&
+                   config_methods >= 0 && (config_methods & 0x0080)) {
+                       menu->addAction(tr("Enroll (PBC)"), this,
+                                       SLOT(connect_pbc()));
+               }
+
+               menu->addAction(tr("Properties"), this, SLOT(properties()));
        } else {
                ctx_item = NULL;
                menu->addAction(QString("Refresh"), this, SLOT(ctx_refresh()));
@@ -128,7 +180,15 @@ void Peers::enter_pin()
 {
        if (ctx_item == NULL)
                return;
-       QString addr = ctx_item->data(peer_role_address).toString();
+
+       int peer_type = ctx_item->data(peer_role_type).toInt();
+       QString uuid;
+       QString addr;
+       if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE)
+               uuid = ctx_item->data(peer_role_uuid).toString();
+       else
+               addr = ctx_item->data(peer_role_address).toString();
+
        StringQuery input(tr("PIN:"));
        input.setWindowTitle(tr("PIN for ") + ctx_item->text());
        if (input.exec() != QDialog::Accepted)
@@ -137,9 +197,16 @@ void Peers::enter_pin()
        char cmd[100];
        char reply[100];
        size_t reply_len;
-       snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
-                addr.toAscii().constData(),
-                input.get_string().toAscii().constData());
+
+       if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
+               snprintf(cmd, sizeof(cmd), "WPS_ER_PIN %s %s",
+                        uuid.toAscii().constData(),
+                        input.get_string().toAscii().constData());
+       } else {
+               snprintf(cmd, sizeof(cmd), "WPS_PIN %s %s",
+                        addr.toAscii().constData(),
+                        input.get_string().toAscii().constData());
+       }
        reply_len = sizeof(reply) - 1;
        if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
                QMessageBox msg;
@@ -174,12 +241,13 @@ void Peers::add_station(QString info)
        if (name.isEmpty())
                name = lines[0];
 
-       QStandardItem *item = new QStandardItem(*default_icon, name);
+       QStandardItem *item = new QStandardItem(*laptop_icon, name);
        if (item) {
                item->setData(lines[0], peer_role_address);
                item->setData(PEER_TYPE_ASSOCIATED_STATION,
                              peer_role_type);
-               item->setToolTip(info);
+               item->setData(info, peer_role_details);
+               item->setToolTip(ItemType(PEER_TYPE_ASSOCIATED_STATION));
                model.appendRow(item);
        }
 }
@@ -263,7 +331,7 @@ void Peers::add_scan_results()
                if (bss.isEmpty() || bss.startsWith("FAIL"))
                        break;
 
-               QString ssid, bssid, flags, wps_name;
+               QString ssid, bssid, flags, wps_name, pri_dev_type;
 
                QStringList lines = bss.split(QRegExp("\\n"));
                for (QStringList::Iterator it = lines.begin();
@@ -280,20 +348,23 @@ void Peers::add_scan_results()
                                ssid = (*it).mid(pos);
                        else if ((*it).startsWith("wps_device_name="))
                                wps_name = (*it).mid(pos);
+                       else if ((*it).startsWith("wps_primary_device_type="))
+                               pri_dev_type = (*it).mid(pos);
                }
 
                QString name = wps_name;
                if (name.isEmpty())
                        name = ssid + "\n" + bssid;
 
-               QStandardItem *item = new QStandardItem(*default_icon, name);
+               QStandardItem *item = new QStandardItem(*ap_icon, name);
                if (item) {
                        item->setData(bssid, peer_role_address);
+                       int type;
                        if (flags.contains("[WPS"))
-                               item->setData(PEER_TYPE_AP_WPS,
-                                             peer_role_type);
+                               type = PEER_TYPE_AP_WPS;
                        else
-                               item->setData(PEER_TYPE_AP, peer_role_type);
+                               type = PEER_TYPE_AP;
+                       item->setData(type, peer_role_type);
 
                        for (int i = 0; i < lines.size(); i++) {
                                if (lines[i].length() > 60) {
@@ -302,7 +373,13 @@ void Peers::add_scan_results()
                                        lines[i] += "..";
                                }
                        }
-                       item->setToolTip(lines.join("\n"));
+                       item->setToolTip(ItemType(type));
+                       item->setData(lines.join("\n"), peer_role_details);
+                       if (!pri_dev_type.isEmpty())
+                               item->setData(pri_dev_type,
+                                             peer_role_pri_dev_type);
+                       if (!ssid.isEmpty())
+                               item->setData(ssid, peer_role_ssid);
                        model.appendRow(item);
                }
        }
@@ -315,6 +392,10 @@ void Peers::update_peers()
        if (wpagui == NULL)
                return;
 
+       char reply[20];
+       size_t replylen = sizeof(reply) - 1;
+       wpagui->ctrlRequest("WPS_ER_START", reply, &replylen);
+
        add_stations();
        add_scan_results();
 }
@@ -333,6 +414,19 @@ QStandardItem * Peers::find_addr(QString addr)
 }
 
 
+QStandardItem * Peers::find_uuid(QString uuid)
+{
+       if (model.rowCount() == 0)
+               return NULL;
+
+       QModelIndexList lst = model.match(model.index(0, 0), peer_role_uuid,
+                                         uuid);
+       if (lst.size() == 0)
+               return NULL;
+       return model.itemFromIndex(lst[0]);
+}
+
+
 void Peers::event_notify(WpaMsg msg)
 {
        QString text = msg.getMsg();
@@ -363,12 +457,14 @@ void Peers::event_notify(WpaMsg msg)
                        }
                }
 
-               item = new QStandardItem(*default_icon, name);
+               item = new QStandardItem(*laptop_icon, name);
                if (item) {
                        item->setData(addr, peer_role_address);
                        item->setData(PEER_TYPE_WPS_PIN_NEEDED,
                                      peer_role_type);
-                       item->setToolTip(items.join(QString("\n")));
+                       item->setToolTip(ItemType(PEER_TYPE_WPS_PIN_NEEDED));
+                       item->setData(items.join("\n"), peer_role_details);
+                       item->setData(items[5], peer_role_pri_dev_type);
                        model.appendRow(item);
                }
                return;
@@ -403,4 +499,301 @@ void Peers::event_notify(WpaMsg msg)
                }
                return;
        }
+
+       if (text.startsWith(WPS_EVENT_ER_AP_ADD)) {
+               /*
+                * WPS-ER-AP-ADD 87654321-9abc-def0-1234-56789abc0002
+                * 02:11:22:33:44:55 pri_dev_type=6-0050F204-1 wps_state=1
+                * |Very friendly name|Company|Long description of the model|
+                * WAP|http://w1.fi/|http://w1.fi/hostapd/
+                */
+               QStringList items = text.split(' ');
+               if (items.size() < 5)
+                       return;
+               QString uuid = items[1];
+               QString addr = items[2];
+               QString pri_dev_type = items[3].mid(13);
+               int wps_state = items[4].mid(10).toInt();
+
+               int pos = text.indexOf('|');
+               if (pos < 0)
+                       return;
+               items = text.mid(pos + 1).split('|');
+               if (items.size() < 1)
+                       return;
+
+               QStandardItem *item = find_uuid(uuid);
+               if (item)
+                       return;
+
+               item = new QStandardItem(*ap_icon, items[0]);
+               if (item) {
+                       item->setData(uuid, peer_role_uuid);
+                       item->setData(addr, peer_role_address);
+                       int type = wps_state == 2 ? PEER_TYPE_WPS_ER_AP:
+                               PEER_TYPE_WPS_ER_AP_UNCONFIGURED;
+                       item->setData(type, peer_role_type);
+                       item->setToolTip(ItemType(type));
+                       item->setData(pri_dev_type, peer_role_pri_dev_type);
+                       item->setData(items.join(QString("\n")),
+                                     peer_role_details);
+                       model.appendRow(item);
+               }
+
+               return;
+       }
+
+       if (text.startsWith(WPS_EVENT_ER_AP_REMOVE)) {
+               /* WPS-ER-AP-REMOVE 87654321-9abc-def0-1234-56789abc0002 */
+               QStringList items = text.split(' ');
+               if (items.size() < 2)
+                       return;
+               if (model.rowCount() == 0)
+                       return;
+
+               QModelIndexList lst = model.match(model.index(0, 0),
+                                                 peer_role_uuid, items[1]);
+               for (int i = 0; i < lst.size(); i++) {
+                       QStandardItem *item = model.itemFromIndex(lst[i]);
+                       if (item &&
+                           (item->data(peer_role_type).toInt() ==
+                            PEER_TYPE_WPS_ER_AP ||
+                            item->data(peer_role_type).toInt() ==
+                            PEER_TYPE_WPS_ER_AP_UNCONFIGURED))
+                               model.removeRow(lst[i].row());
+               }
+               return;
+       }
+
+       if (text.startsWith(WPS_EVENT_ER_ENROLLEE_ADD)) {
+               /*
+                * WPS-ER-ENROLLEE-ADD 2b7093f1-d6fb-5108-adbb-bea66bb87333
+                * 02:66:a0:ee:17:27 M1=1 config_methods=0x14d dev_passwd_id=0
+                * pri_dev_type=1-0050F204-1
+                * |Wireless Client|Company|cmodel|123|12345|
+                */
+               QStringList items = text.split(' ');
+               if (items.size() < 3)
+                       return;
+               QString uuid = items[1];
+               QString addr = items[2];
+               QString pri_dev_type = items[6].mid(13);
+               int config_methods = -1;
+               int dev_passwd_id = -1;
+
+               for (int i = 3; i < items.size(); i++) {
+                       int pos = items[i].indexOf('=') + 1;
+                       if (pos < 1)
+                               continue;
+                       QString val = items[i].mid(pos);
+                       if (items[i].startsWith("config_methods=")) {
+                               config_methods = val.toInt(0, 0);
+                       } else if (items[i].startsWith("dev_passwd_id=")) {
+                               dev_passwd_id = val.toInt();
+                       }
+               }
+
+               int pos = text.indexOf('|');
+               if (pos < 0)
+                       return;
+               items = text.mid(pos + 1).split('|');
+               if (items.size() < 1)
+                       return;
+               QString name = items[0];
+               if (name.length() == 0)
+                       name = addr;
+
+               remove_enrollee_uuid(uuid);
+
+               QStandardItem *item;
+               item = new QStandardItem(*laptop_icon, name);
+               if (item) {
+                       item->setData(uuid, peer_role_uuid);
+                       item->setData(addr, peer_role_address);
+                       item->setData(PEER_TYPE_WPS_ER_ENROLLEE,
+                                     peer_role_type);
+                       item->setToolTip(ItemType(PEER_TYPE_WPS_ER_ENROLLEE));
+                       item->setData(items.join(QString("\n")),
+                                     peer_role_details);
+                       item->setData(pri_dev_type, peer_role_pri_dev_type);
+                       if (config_methods >= 0)
+                               item->setData(config_methods,
+                                             peer_role_config_methods);
+                       if (dev_passwd_id >= 0)
+                               item->setData(dev_passwd_id,
+                                             peer_role_dev_passwd_id);
+                       model.appendRow(item);
+               }
+
+               return;
+       }
+
+       if (text.startsWith(WPS_EVENT_ER_ENROLLEE_REMOVE)) {
+               /*
+                * WPS-ER-ENROLLEE-REMOVE 2b7093f1-d6fb-5108-adbb-bea66bb87333
+                * 02:66:a0:ee:17:27
+                */
+               QStringList items = text.split(' ');
+               if (items.size() < 2)
+                       return;
+               remove_enrollee_uuid(items[1]);
+               return;
+       }
+}
+
+
+void Peers::closeEvent(QCloseEvent *)
+{
+       if (wpagui) {
+               char reply[20];
+               size_t replylen = sizeof(reply) - 1;
+               wpagui->ctrlRequest("WPS_ER_STOP", reply, &replylen);
+       }
+}
+
+
+void Peers::done(int r)
+{
+       QDialog::done(r);
+       close();
+}
+
+
+void Peers::remove_enrollee_uuid(QString uuid)
+{
+       if (model.rowCount() == 0)
+               return;
+
+       QModelIndexList lst = model.match(model.index(0, 0),
+                                         peer_role_uuid, uuid);
+       for (int i = 0; i < lst.size(); i++) {
+               QStandardItem *item = model.itemFromIndex(lst[i]);
+               if (item && item->data(peer_role_type).toInt() ==
+                   PEER_TYPE_WPS_ER_ENROLLEE)
+                       model.removeRow(lst[i].row());
+       }
+}
+
+
+void Peers::properties()
+{
+       if (ctx_item == NULL)
+               return;
+
+       QMessageBox msg(this);
+       msg.setStandardButtons(QMessageBox::Ok);
+       msg.setDefaultButton(QMessageBox::Ok);
+       msg.setEscapeButton(QMessageBox::Ok);
+       msg.setWindowTitle(tr("Peer Properties"));
+
+       int type = ctx_item->data(peer_role_type).toInt();
+       QString title = Peers::ItemType(type);
+
+       msg.setText(title + QString("\n") + tr("Name: ") + ctx_item->text());
+
+       QVariant var;
+       QString info;
+
+       var = ctx_item->data(peer_role_address);
+       if (var.isValid())
+               info += tr("Address: ") + var.toString() + QString("\n");
+
+       var = ctx_item->data(peer_role_uuid);
+       if (var.isValid())
+               info += tr("UUID: ") + var.toString() + QString("\n");
+
+       var = ctx_item->data(peer_role_pri_dev_type);
+       if (var.isValid())
+               info += tr("Primary Device Type: ") + var.toString() +
+                       QString("\n");
+
+       var = ctx_item->data(peer_role_ssid);
+       if (var.isValid())
+               info += tr("SSID: ") + var.toString() + QString("\n");
+
+       var = ctx_item->data(peer_role_config_methods);
+       if (var.isValid()) {
+               int methods = var.toInt();
+               info += tr("Configuration Methods: ");
+               if (methods & 0x0001)
+                       info += tr("[USBA]");
+               if (methods & 0x0002)
+                       info += tr("[Ethernet]");
+               if (methods & 0x0004)
+                       info += tr("[Label]");
+               if (methods & 0x0008)
+                       info += tr("[Display]");
+               if (methods & 0x0010)
+                       info += tr("[Ext. NFC Token]");
+               if (methods & 0x0020)
+                       info += tr("[Int. NFC Token]");
+               if (methods & 0x0040)
+                       info += tr("[NFC Interface]");
+               if (methods & 0x0080)
+                       info += tr("[Push Button]");
+               if (methods & 0x0100)
+                       info += tr("[Keypad]");
+               info += "\n";
+       }
+
+       var = ctx_item->data(peer_role_dev_passwd_id);
+       if (var.isValid()) {
+               info += tr("Device Password ID: ") + var.toString();
+               switch (var.toInt()) {
+               case 0:
+                       info += tr(" (Default PIN)");
+                       break;
+               case 1:
+                       info += tr(" (User-specified PIN)");
+                       break;
+               case 2:
+                       info += tr(" (Machine-specified PIN)");
+                       break;
+               case 3:
+                       info += tr(" (Rekey)");
+                       break;
+               case 4:
+                       info += tr(" (Push Button)");
+                       break;
+               case 5:
+                       info += tr(" (Registrar-specified)");
+                       break;
+               }
+               info += "\n";
+       }
+
+       msg.setInformativeText(info);
+
+       var = ctx_item->data(peer_role_details);
+       if (var.isValid())
+               msg.setDetailedText(var.toString());
+
+       msg.exec();
+}
+
+
+void Peers::connect_pbc()
+{
+       if (ctx_item == NULL)
+               return;
+
+       char cmd[100];
+       char reply[100];
+       size_t reply_len;
+
+       int peer_type = ctx_item->data(peer_role_type).toInt();
+       if (peer_type == PEER_TYPE_WPS_ER_ENROLLEE) {
+               snprintf(cmd, sizeof(cmd), "WPS_ER_PBC %s",
+                        ctx_item->data(peer_role_uuid).toString().toAscii().
+                        constData());
+       } else {
+               snprintf(cmd, sizeof(cmd), "WPS_PBC");
+       }
+       reply_len = sizeof(reply) - 1;
+       if (wpagui->ctrlRequest(cmd, reply, &reply_len) < 0) {
+               QMessageBox msg;
+               msg.setIcon(QMessageBox::Warning);
+               msg.setText("Failed to start WPS PBC.");
+               msg.exec();
+       }
 }