peers.cpp 14 KB

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