1 /* 2 * wpa_gui - WpaGui class 3 * Copyright (c) 2005-2008, Jouni Malinen <j (at) w1.fi> 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 #ifdef __MINGW32__ 16 /* Need to get getopt() */ 17 #include <unistd.h> 18 #endif 19 20 #include <cstdio> 21 #include <QMessageBox> 22 23 #include "wpagui.h" 24 #include "dirent.h" 25 #include "wpa_ctrl.h" 26 #include "userdatarequest.h" 27 #include "networkconfig.h" 28 29 WpaGui::WpaGui(QWidget *parent, const char *, Qt::WFlags) 30 : QMainWindow(parent) 31 { 32 setupUi(this); 33 34 (void) statusBar(); 35 36 connect(helpIndexAction, SIGNAL(activated()), this, SLOT(helpIndex())); 37 connect(helpContentsAction, SIGNAL(activated()), this, 38 SLOT(helpContents())); 39 connect(helpAboutAction, SIGNAL(activated()), this, SLOT(helpAbout())); 40 connect(fileExitAction, SIGNAL(activated()), this, SLOT(close())); 41 connect(disconnectButton, SIGNAL(clicked()), this, SLOT(disconnect())); 42 connect(scanButton, SIGNAL(clicked()), this, SLOT(scan())); 43 connect(connectButton, SIGNAL(clicked()), this, SLOT(connectB())); 44 connect(fileEventHistoryAction, SIGNAL(activated()), this, 45 SLOT(eventHistory())); 46 connect(networkSelect, SIGNAL(activated(const QString&)), this, 47 SLOT(selectNetwork(const QString&))); 48 connect(fileEdit_networkAction, SIGNAL(activated()), this, 49 SLOT(editNetwork())); 50 connect(fileAdd_NetworkAction, SIGNAL(activated()), this, 51 SLOT(addNetwork())); 52 connect(adapterSelect, SIGNAL(activated(const QString&)), this, 53 SLOT(selectAdapter(const QString&))); 54 55 eh = NULL; 56 scanres = NULL; 57 udr = NULL; 58 ctrl_iface = NULL; 59 ctrl_conn = NULL; 60 monitor_conn = NULL; 61 msgNotifier = NULL; 62 ctrl_iface_dir = strdup("/var/run/wpa_supplicant"); 63 64 parse_argv(); 65 66 textStatus->setText("connecting to wpa_supplicant"); 67 timer = new QTimer(this); 68 connect(timer, SIGNAL(timeout()), SLOT(ping())); 69 timer->start(1000, FALSE); 70 71 if (openCtrlConnection(ctrl_iface) < 0) { 72 printf("Failed to open control connection to " 73 "wpa_supplicant.\n"); 74 } 75 76 updateStatus(); 77 networkMayHaveChanged = true; 78 updateNetworks(); 79 } 80 81 82 WpaGui::~WpaGui() 83 { 84 delete msgNotifier; 85 86 if (monitor_conn) { 87 wpa_ctrl_detach(monitor_conn); 88 wpa_ctrl_close(monitor_conn); 89 monitor_conn = NULL; 90 } 91 if (ctrl_conn) { 92 wpa_ctrl_close(ctrl_conn); 93 ctrl_conn = NULL; 94 } 95 96 if (eh) { 97 eh->close(); 98 delete eh; 99 eh = NULL; 100 } 101 102 if (scanres) { 103 scanres->close(); 104 delete scanres; 105 scanres = NULL; 106 } 107 108 if (udr) { 109 udr->close(); 110 delete udr; 111 udr = NULL; 112 } 113 114 free(ctrl_iface); 115 ctrl_iface = NULL; 116 117 free(ctrl_iface_dir); 118 ctrl_iface_dir = NULL; 119 } 120 121 122 void WpaGui::languageChange() 123 { 124 retranslateUi(this); 125 } 126 127 128 void WpaGui::parse_argv() 129 { 130 int c; 131 for (;;) { 132 c = getopt(qApp->argc(), qApp->argv(), "i:p:"); 133 if (c < 0) 134 break; 135 switch (c) { 136 case 'i': 137 free(ctrl_iface); 138 ctrl_iface = strdup(optarg); 139 break; 140 case 'p': 141 free(ctrl_iface_dir); 142 ctrl_iface_dir = strdup(optarg); 143 break; 144 } 145 } 146 } 147 148 149 int WpaGui::openCtrlConnection(const char *ifname) 150 { 151 char *cfile; 152 int flen; 153 char buf[2048], *pos, *pos2; 154 size_t len; 155 156 if (ifname) { 157 if (ifname != ctrl_iface) { 158 free(ctrl_iface); 159 ctrl_iface = strdup(ifname); 160 } 161 } else { 162 #ifdef CONFIG_CTRL_IFACE_UDP 163 free(ctrl_iface); 164 ctrl_iface = strdup("udp"); 165 #endif /* CONFIG_CTRL_IFACE_UDP */ 166 #ifdef CONFIG_CTRL_IFACE_UNIX 167 struct dirent *dent; 168 DIR *dir = opendir(ctrl_iface_dir); 169 free(ctrl_iface); 170 ctrl_iface = NULL; 171 if (dir) { 172 while ((dent = readdir(dir))) { 173 #ifdef _DIRENT_HAVE_D_TYPE 174 /* Skip the file if it is not a socket. 175 * Also accept DT_UNKNOWN (0) in case 176 * the C library or underlying file 177 * system does not support d_type. */ 178 if (dent->d_type != DT_SOCK && 179 dent->d_type != DT_UNKNOWN) 180 continue; 181 #endif /* _DIRENT_HAVE_D_TYPE */ 182 183 if (strcmp(dent->d_name, ".") == 0 || 184 strcmp(dent->d_name, "..") == 0) 185 continue; 186 printf("Selected interface '%s'\n", 187 dent->d_name); 188 ctrl_iface = strdup(dent->d_name); 189 break; 190 } 191 closedir(dir); 192 } 193 #endif /* CONFIG_CTRL_IFACE_UNIX */ 194 #ifdef CONFIG_CTRL_IFACE_NAMED_PIPE 195 struct wpa_ctrl *ctrl; 196 int ret; 197 198 free(ctrl_iface); 199 ctrl_iface = NULL; 200 201 ctrl = wpa_ctrl_open(NULL); 202 if (ctrl) { 203 len = sizeof(buf) - 1; 204 ret = wpa_ctrl_request(ctrl, "INTERFACES", 10, buf, 205 &len, NULL); 206 if (ret >= 0) { 207 buf[len] = '\0'; 208 pos = strchr(buf, '\n'); 209 if (pos) 210 *pos = '\0'; 211 ctrl_iface = strdup(buf); 212 } 213 wpa_ctrl_close(ctrl); 214 } 215 #endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */ 216 } 217 218 if (ctrl_iface == NULL) 219 return -1; 220 221 #ifdef CONFIG_CTRL_IFACE_UNIX 222 flen = strlen(ctrl_iface_dir) + strlen(ctrl_iface) + 2; 223 cfile = (char *) malloc(flen); 224 if (cfile == NULL) 225 return -1; 226 snprintf(cfile, flen, "%s/%s", ctrl_iface_dir, ctrl_iface); 227 #else /* CONFIG_CTRL_IFACE_UNIX */ 228 flen = strlen(ctrl_iface) + 1; 229 cfile = (char *) malloc(flen); 230 if (cfile == NULL) 231 return -1; 232 snprintf(cfile, flen, "%s", ctrl_iface); 233 #endif /* CONFIG_CTRL_IFACE_UNIX */ 234 235 if (ctrl_conn) { 236 wpa_ctrl_close(ctrl_conn); 237 ctrl_conn = NULL; 238 } 239 240 if (monitor_conn) { 241 delete msgNotifier; 242 msgNotifier = NULL; 243 wpa_ctrl_detach(monitor_conn); 244 wpa_ctrl_close(monitor_conn); 245 monitor_conn = NULL; 246 } 247 248 printf("Trying to connect to '%s'\n", cfile); 249 ctrl_conn = wpa_ctrl_open(cfile); 250 if (ctrl_conn == NULL) { 251 free(cfile); 252 return -1; 253 } 254 monitor_conn = wpa_ctrl_open(cfile); 255 free(cfile); 256 if (monitor_conn == NULL) { 257 wpa_ctrl_close(ctrl_conn); 258 return -1; 259 } 260 if (wpa_ctrl_attach(monitor_conn)) { 261 printf("Failed to attach to wpa_supplicant\n"); 262 wpa_ctrl_close(monitor_conn); 263 monitor_conn = NULL; 264 wpa_ctrl_close(ctrl_conn); 265 ctrl_conn = NULL; 266 return -1; 267 } 268 269 #if defined(CONFIG_CTRL_IFACE_UNIX) || defined(CONFIG_CTRL_IFACE_UDP) 270 msgNotifier = new QSocketNotifier(wpa_ctrl_get_fd(monitor_conn), 271 QSocketNotifier::Read, this); 272 connect(msgNotifier, SIGNAL(activated(int)), SLOT(receiveMsgs())); 273 #endif 274 275 adapterSelect->clear(); 276 adapterSelect->insertItem(ctrl_iface); 277 adapterSelect->setCurrentItem(0); 278 279 len = sizeof(buf) - 1; 280 if (wpa_ctrl_request(ctrl_conn, "INTERFACES", 10, buf, &len, NULL) >= 281 0) { 282 buf[len] = '\0'; 283 pos = buf; 284 while (*pos) { 285 pos2 = strchr(pos, '\n'); 286 if (pos2) 287 *pos2 = '\0'; 288 if (strcmp(pos, ctrl_iface) != 0) 289 adapterSelect->insertItem(pos); 290 if (pos2) 291 pos = pos2 + 1; 292 else 293 break; 294 } 295 } 296 297 return 0; 298 } 299 300 301 static void wpa_gui_msg_cb(char *msg, size_t) 302 { 303 /* This should not happen anymore since two control connections are 304 * used. */ 305 printf("missed message: %s\n", msg); 306 } 307 308 309 int WpaGui::ctrlRequest(const char *cmd, char *buf, size_t *buflen) 310 { 311 int ret; 312 313 if (ctrl_conn == NULL) 314 return -3; 315 ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), buf, buflen, 316 wpa_gui_msg_cb); 317 if (ret == -2) 318 printf("'%s' command timed out.\n", cmd); 319 else if (ret < 0) 320 printf("'%s' command failed.\n", cmd); 321 322 return ret; 323 } 324 325 326 void WpaGui::updateStatus() 327 { 328 char buf[2048], *start, *end, *pos; 329 size_t len; 330 331 pingsToStatusUpdate = 10; 332 333 len = sizeof(buf) - 1; 334 if (ctrl_conn == NULL || ctrlRequest("STATUS", buf, &len) < 0) { 335 textStatus->setText("Could not get status from " 336 "wpa_supplicant"); 337 textAuthentication->clear(); 338 textEncryption->clear(); 339 textSsid->clear(); 340 textBssid->clear(); 341 textIpAddress->clear(); 342 return; 343 } 344 345 buf[len] = '\0'; 346 347 bool auth_updated = false, ssid_updated = false; 348 bool bssid_updated = false, ipaddr_updated = false; 349 bool status_updated = false; 350 char *pairwise_cipher = NULL, *group_cipher = NULL; 351 352 start = buf; 353 while (*start) { 354 bool last = false; 355 end = strchr(start, '\n'); 356 if (end == NULL) { 357 last = true; 358 end = start; 359 while (end[0] && end[1]) 360 end++; 361 } 362 *end = '\0'; 363 364 pos = strchr(start, '='); 365 if (pos) { 366 *pos++ = '\0'; 367 if (strcmp(start, "bssid") == 0) { 368 bssid_updated = true; 369 textBssid->setText(pos); 370 } else if (strcmp(start, "ssid") == 0) { 371 ssid_updated = true; 372 textSsid->setText(pos); 373 } else if (strcmp(start, "ip_address") == 0) { 374 ipaddr_updated = true; 375 textIpAddress->setText(pos); 376 } else if (strcmp(start, "wpa_state") == 0) { 377 status_updated = true; 378 textStatus->setText(pos); 379 } else if (strcmp(start, "key_mgmt") == 0) { 380 auth_updated = true; 381 textAuthentication->setText(pos); 382 /* TODO: could add EAP status to this */ 383 } else if (strcmp(start, "pairwise_cipher") == 0) { 384 pairwise_cipher = pos; 385 } else if (strcmp(start, "group_cipher") == 0) { 386 group_cipher = pos; 387 } 388 } 389 390 if (last) 391 break; 392 start = end + 1; 393 } 394 395 if (pairwise_cipher || group_cipher) { 396 QString encr; 397 if (pairwise_cipher && group_cipher && 398 strcmp(pairwise_cipher, group_cipher) != 0) { 399 encr.append(pairwise_cipher); 400 encr.append(" + "); 401 encr.append(group_cipher); 402 } else if (pairwise_cipher) { 403 encr.append(pairwise_cipher); 404 } else if (group_cipher) { 405 encr.append(group_cipher); 406 encr.append(" [group key only]"); 407 } else { 408 encr.append("?"); 409 } 410 textEncryption->setText(encr); 411 } else 412 textEncryption->clear(); 413 414 if (!status_updated) 415 textStatus->clear(); 416 if (!auth_updated) 417 textAuthentication->clear(); 418 if (!ssid_updated) 419 textSsid->clear(); 420 if (!bssid_updated) 421 textBssid->clear(); 422 if (!ipaddr_updated) 423 textIpAddress->clear(); 424 } 425 426 427 void WpaGui::updateNetworks() 428 { 429 char buf[2048], *start, *end, *id, *ssid, *bssid, *flags; 430 size_t len; 431 int first_active = -1; 432 bool selected = false; 433 434 if (!networkMayHaveChanged) 435 return; 436 437 networkSelect->clear(); 438 439 if (ctrl_conn == NULL) 440 return; 441 442 len = sizeof(buf) - 1; 443 if (ctrlRequest("LIST_NETWORKS", buf, &len) < 0) 444 return; 445 446 buf[len] = '\0'; 447 start = strchr(buf, '\n'); 448 if (start == NULL) 449 return; 450 start++; 451 452 while (*start) { 453 bool last = false; 454 end = strchr(start, '\n'); 455 if (end == NULL) { 456 last = true; 457 end = start; 458 while (end[0] && end[1]) 459 end++; 460 } 461 *end = '\0'; 462 463 id = start; 464 ssid = strchr(id, '\t'); 465 if (ssid == NULL) 466 break; 467 *ssid++ = '\0'; 468 bssid = strchr(ssid, '\t'); 469 if (bssid == NULL) 470 break; 471 *bssid++ = '\0'; 472 flags = strchr(bssid, '\t'); 473 if (flags == NULL) 474 break; 475 *flags++ = '\0'; 476 477 QString network(id); 478 network.append(": "); 479 network.append(ssid); 480 networkSelect->insertItem(network); 481 482 if (strstr(flags, "[CURRENT]")) { 483 networkSelect->setCurrentItem(networkSelect->count() - 484 1); 485 selected = true; 486 } else if (first_active < 0 && 487 strstr(flags, "[DISABLED]") == NULL) 488 first_active = networkSelect->count() - 1; 489 490 if (last) 491 break; 492 start = end + 1; 493 } 494 495 if (!selected && first_active >= 0) 496 networkSelect->setCurrentItem(first_active); 497 498 networkMayHaveChanged = false; 499 } 500 501 502 void WpaGui::helpIndex() 503 { 504 printf("helpIndex\n"); 505 } 506 507 508 void WpaGui::helpContents() 509 { 510 printf("helpContents\n"); 511 } 512 513 514 void WpaGui::helpAbout() 515 { 516 QMessageBox::about(this, "wpa_gui for wpa_supplicant", 517 "Copyright (c) 2003-2008,\n" 518 "Jouni Malinen <j (at) w1.fi>\n" 519 "and contributors.\n" 520 "\n" 521 "This program is free software. You can\n" 522 "distribute it and/or modify it under the terms " 523 "of\n" 524 "the GNU General Public License version 2.\n" 525 "\n" 526 "Alternatively, this software may be distributed\n" 527 "under the terms of the BSD license.\n" 528 "\n" 529 "This product includes software developed\n" 530 "by the OpenSSL Project for use in the\n" 531 "OpenSSL Toolkit (http://www.openssl.org/)\n"); 532 } 533 534 535 void WpaGui::disconnect() 536 { 537 char reply[10]; 538 size_t reply_len = sizeof(reply); 539 ctrlRequest("DISCONNECT", reply, &reply_len); 540 } 541 542 543 void WpaGui::scan() 544 { 545 if (scanres) { 546 scanres->close(); 547 delete scanres; 548 } 549 550 scanres = new ScanResults(); 551 if (scanres == NULL) 552 return; 553 scanres->setWpaGui(this); 554 scanres->show(); 555 scanres->exec(); 556 } 557 558 559 void WpaGui::eventHistory() 560 { 561 if (eh) { 562 eh->close(); 563 delete eh; 564 } 565 566 eh = new EventHistory(); 567 if (eh == NULL) 568 return; 569 eh->addEvents(msgs); 570 eh->show(); 571 eh->exec(); 572 } 573 574 575 void WpaGui::ping() 576 { 577 char buf[10]; 578 size_t len; 579 580 #ifdef CONFIG_CTRL_IFACE_NAMED_PIPE 581 /* 582 * QSocketNotifier cannot be used with Windows named pipes, so use a 583 * timer to check for received messages for now. This could be 584 * optimized be doing something specific to named pipes or Windows 585 * events, but it is not clear what would be the best way of doing that 586 * in Qt. 587 */ 588 receiveMsgs(); 589 #endif /* CONFIG_CTRL_IFACE_NAMED_PIPE */ 590 591 if (scanres && !scanres->isVisible()) { 592 delete scanres; 593 scanres = NULL; 594 } 595 596 if (eh && !eh->isVisible()) { 597 delete eh; 598 eh = NULL; 599 } 600 601 if (udr && !udr->isVisible()) { 602 delete udr; 603 udr = NULL; 604 } 605 606 len = sizeof(buf) - 1; 607 if (ctrlRequest("PING", buf, &len) < 0) { 608 printf("PING failed - trying to reconnect\n"); 609 if (openCtrlConnection(ctrl_iface) >= 0) { 610 printf("Reconnected successfully\n"); 611 pingsToStatusUpdate = 0; 612 } 613 } 614 615 pingsToStatusUpdate--; 616 if (pingsToStatusUpdate <= 0) { 617 updateStatus(); 618 updateNetworks(); 619 } 620 } 621 622 623 static int str_match(const char *a, const char *b) 624 { 625 return strncmp(a, b, strlen(b)) == 0; 626 } 627 628 629 void WpaGui::processMsg(char *msg) 630 { 631 char *pos = msg, *pos2; 632 int priority = 2; 633 634 if (*pos == '<') { 635 /* skip priority */ 636 pos++; 637 priority = atoi(pos); 638 pos = strchr(pos, '>'); 639 if (pos) 640 pos++; 641 else 642 pos = msg; 643 } 644 645 WpaMsg wm(pos, priority); 646 if (eh) 647 eh->addEvent(wm); 648 msgs.append(wm); 649 while (msgs.count() > 100) 650 msgs.pop_front(); 651 652 /* Update last message with truncated version of the event */ 653 if (strncmp(pos, "CTRL-", 5) == 0) { 654 pos2 = strchr(pos, str_match(pos, WPA_CTRL_REQ) ? ':' : ' '); 655 if (pos2) 656 pos2++; 657 else 658 pos2 = pos; 659 } else 660 pos2 = pos; 661 QString lastmsg = pos2; 662 lastmsg.truncate(40); 663 textLastMessage->setText(lastmsg); 664 665 pingsToStatusUpdate = 0; 666 networkMayHaveChanged = true; 667 668 if (str_match(pos, WPA_CTRL_REQ)) 669 processCtrlReq(pos + strlen(WPA_CTRL_REQ)); 670 } 671 672 673 void WpaGui::processCtrlReq(const char *req) 674 { 675 if (udr) { 676 udr->close(); 677 delete udr; 678 } 679 udr = new UserDataRequest(); 680 if (udr == NULL) 681 return; 682 if (udr->setParams(this, req) < 0) { 683 delete udr; 684 udr = NULL; 685 return; 686 } 687 udr->show(); 688 udr->exec(); 689 } 690 691 692 void WpaGui::receiveMsgs() 693 { 694 char buf[256]; 695 size_t len; 696 697 while (monitor_conn && wpa_ctrl_pending(monitor_conn) > 0) { 698 len = sizeof(buf) - 1; 699 if (wpa_ctrl_recv(monitor_conn, buf, &len) == 0) { 700 buf[len] = '\0'; 701 processMsg(buf); 702 } 703 } 704 } 705 706 707 void WpaGui::connectB() 708 { 709 char reply[10]; 710 size_t reply_len = sizeof(reply); 711 ctrlRequest("REASSOCIATE", reply, &reply_len); 712 } 713 714 715 void WpaGui::selectNetwork( const QString &sel ) 716 { 717 QString cmd(sel); 718 char reply[10]; 719 size_t reply_len = sizeof(reply); 720 721 int pos = cmd.find(':'); 722 if (pos < 0) { 723 printf("Invalid selectNetwork '%s'\n", cmd.ascii()); 724 return; 725 } 726 cmd.truncate(pos); 727 cmd.prepend("SELECT_NETWORK "); 728 ctrlRequest(cmd.ascii(), reply, &reply_len); 729 } 730 731 732 void WpaGui::editNetwork() 733 { 734 QString sel(networkSelect->currentText()); 735 int pos = sel.find(':'); 736 if (pos < 0) { 737 printf("Invalid selectNetwork '%s'\n", sel.ascii()); 738 return; 739 } 740 sel.truncate(pos); 741 742 NetworkConfig *nc = new NetworkConfig(); 743 if (nc == NULL) 744 return; 745 nc->setWpaGui(this); 746 747 nc->paramsFromConfig(sel.toInt()); 748 nc->show(); 749 nc->exec(); 750 } 751 752 753 void WpaGui::triggerUpdate() 754 { 755 updateStatus(); 756 networkMayHaveChanged = true; 757 updateNetworks(); 758 } 759 760 761 void WpaGui::addNetwork() 762 { 763 NetworkConfig *nc = new NetworkConfig(); 764 if (nc == NULL) 765 return; 766 nc->setWpaGui(this); 767 nc->newNetwork(); 768 nc->show(); 769 nc->exec(); 770 } 771 772 773 void WpaGui::selectAdapter( const QString & sel ) 774 { 775 if (openCtrlConnection(sel.ascii()) < 0) 776 printf("Failed to open control connection to " 777 "wpa_supplicant.\n"); 778 updateStatus(); 779 updateNetworks(); 780 } 781