summaryrefslogtreecommitdiffstats
path: root/src/networksettings/wpasupplicant/qwifisupplicant.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/networksettings/wpasupplicant/qwifisupplicant.cpp')
-rw-r--r--src/networksettings/wpasupplicant/qwifisupplicant.cpp443
1 files changed, 443 insertions, 0 deletions
diff --git a/src/networksettings/wpasupplicant/qwifisupplicant.cpp b/src/networksettings/wpasupplicant/qwifisupplicant.cpp
new file mode 100644
index 0000000..779475e
--- /dev/null
+++ b/src/networksettings/wpasupplicant/qwifisupplicant.cpp
@@ -0,0 +1,443 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Digia Plc
+** All rights reserved.
+** For any questions to Digia, please use the contact form at
+** http://www.qt.io
+**
+** This file is part of Qt Enterprise Embedded.
+**
+** Licensees holding valid Qt Enterprise licenses may use this file in
+** accordance with the Qt Enterprise License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia.
+**
+** If you have questions regarding the use of this file, please use
+** the contact form at http://www.qt.io
+**
+****************************************************************************/
+#include "qwifisupplicant_p.h"
+#include "qwifidevice.h"
+
+#include <poll.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+#include <QtCore/QFile>
+#include <QtCore/QProcess>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(B2QT_WIFI_VERBOSE, "qt.b2qt.wifi.verbose")
+
+#define CONFIG_FILE "/etc/wpa_supplicant.qtwifi.conf"
+#define CONTROL_INTERFACE_PATH "/var/run/wpa_supplicant/"
+
+QWifiSupplicant::QWifiSupplicant(QObject *parent) :
+ QObject(parent),
+ ctrl_conn(0),
+ monitor_conn(0),
+ interface(QWifiDevice::wifiInterfaceName())
+{
+ createSupplicantConfig();
+}
+
+void QWifiSupplicant::createSupplicantConfig()
+{
+ QFile supplicantConfig(QLatin1String(CONFIG_FILE));
+ if (supplicantConfig.exists())
+ return;
+
+ if (supplicantConfig.open(QIODevice::WriteOnly)) {
+ supplicantConfig.write("ctrl_interface=" CONTROL_INTERFACE_PATH "\n"
+ "ctrl_interface_group=0\n"
+ "update_config=1\n");
+ } else {
+ emit raiseError(QLatin1String("failed to create wpa_supplicant configuration file."));
+ }
+}
+
+bool QWifiSupplicant::startSupplicant()
+{
+ QString pidFile = QLatin1String("/var/run/wpa_supplicant." + interface + ".pid");
+ QString driver(QStringLiteral("nl80211,wext"));
+
+ QStringList arg;
+ arg << QStringLiteral("--start") << QStringLiteral("--quiet") << QStringLiteral("--name");
+ arg << QStringLiteral("wpa_supplicant") << QStringLiteral("--startas");
+ arg << QStringLiteral("/usr/sbin/wpa_supplicant") << QStringLiteral("--pidfile") << pidFile;
+ arg << QStringLiteral("--") << QStringLiteral("-B") << QStringLiteral("-P") << pidFile;
+ arg << QStringLiteral("-i") << QLatin1String(interface) << QStringLiteral("-c");
+ arg << QLatin1String(CONFIG_FILE) << QStringLiteral("-D") << driver;
+
+ QProcess startStopDaemon;
+ startStopDaemon.setProcessChannelMode(QProcess::MergedChannels);
+ startStopDaemon.start(QStringLiteral("start-stop-daemon"), arg);
+ if (!startStopDaemon.waitForStarted()) {
+ emit raiseError(startStopDaemon.program() + QLatin1String(": ") + startStopDaemon.errorString());
+ return false;
+ }
+ startStopDaemon.waitForFinished();
+ // if the interface socket exists then wpa-supplicant was invoked successfully
+ if (!QFile(QLatin1String(CONTROL_INTERFACE_PATH + interface)).exists()) {
+ emit raiseError(QLatin1String("failed to invoke wpa_supplicant: "
+ + startStopDaemon.readAll()));
+ return false;
+ }
+ // reset sockets used for exiting from hung state
+ exit_sockets[0] = exit_sockets[1] = -1;
+ return true;
+}
+
+bool QWifiSupplicant::stopSupplicant()
+{
+ QString pidFile = QLatin1String("/var/run/wpa_supplicant." + interface + ".pid");
+
+ if (QFile(pidFile).exists()) {
+ QStringList arg;
+ arg << QStringLiteral("--stop") << QStringLiteral("--quiet") << QStringLiteral("--name");
+ arg << QStringLiteral("wpa_supplicant") << QStringLiteral("--pidfile") << pidFile;
+
+ QProcess startStopDaemon;
+ startStopDaemon.start(QStringLiteral("start-stop-daemon"), arg);
+ if (!startStopDaemon.waitForStarted()) {
+ emit raiseError(startStopDaemon.program() + QLatin1String(": ") + startStopDaemon.errorString());
+ return false;
+ }
+ startStopDaemon.waitForFinished();
+ QByteArray error = startStopDaemon.readAllStandardError();
+ if (!error.isEmpty()) {
+ emit raiseError(QLatin1String("failed to stop a wpa_supplicant process" + error));
+ return false;
+ }
+
+ QFile::remove(pidFile);
+ }
+
+ QFile::remove(QLatin1String(CONTROL_INTERFACE_PATH + interface));
+
+ // workaround for QTEE-957
+ QProcess killall;
+ killall.start(QStringLiteral("killall"), QStringList() << QStringLiteral("-9") << QStringLiteral("wpa_supplicant"));
+ killall.waitForFinished();
+
+ return true;
+}
+
+/*! \internal
+ *
+ wpa_supplicant socket communication code (Apache License 2.0) with few modifications
+ from https://android.googlesource.com/platform/hardware/libhardware_legacy/
+
+ */
+bool QWifiSupplicant::connectToSupplicant()
+{
+ static char path[4096];
+ snprintf(path, sizeof(path), "%s/%s", CONTROL_INTERFACE_PATH, interface.constData());
+ bool connected = true;
+
+ ctrl_conn = wpa_ctrl_open(path);
+ if (ctrl_conn == NULL) {
+ qCWarning(B2QT_WIFI, "Unable to open connection to wpa_supplicant on \"%s\": %s",
+ path, strerror(errno));
+ connected = false;
+ }
+ monitor_conn = wpa_ctrl_open(path);
+ if (monitor_conn == NULL) {
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = NULL;
+ connected = false;
+ }
+ if (wpa_ctrl_attach(monitor_conn) != 0) {
+ wpa_ctrl_close(monitor_conn);
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = monitor_conn = NULL;
+ connected = false;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, exit_sockets) == -1) {
+ wpa_ctrl_close(monitor_conn);
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = monitor_conn = NULL;
+ connected = false;
+ }
+
+ if (!connected)
+ emit raiseError(QLatin1String("failed to connect to wpa_supplicant"));
+ return connected;
+}
+
+void QWifiSupplicant::closeSupplicantConnection()
+{
+ if (ctrl_conn != NULL) {
+ wpa_ctrl_close(ctrl_conn);
+ ctrl_conn = NULL;
+ }
+
+ if (monitor_conn != NULL) {
+ wpa_ctrl_close(monitor_conn);
+ monitor_conn = NULL;
+ }
+
+ if (exit_sockets[0] >= 0) {
+ close(exit_sockets[0]);
+ exit_sockets[0] = -1;
+ }
+
+ if (exit_sockets[1] >= 0) {
+ close(exit_sockets[1]);
+ exit_sockets[1] = -1;
+ }
+}
+
+int QWifiSupplicant::waitForEvent(char *buf, size_t buflen)
+{
+ size_t nread = buflen - 1;
+ int result;
+ char *match, *match2;
+
+ if (monitor_conn == NULL)
+ return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - connection closed");
+
+ result = receiveEvent(buf, &nread);
+
+ // Terminate reception on exit socket
+ if (result == -2)
+ return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - connection closed");
+
+ if (result < 0) {
+ qCWarning(B2QT_WIFI, "receiveEvent failed: %s", strerror(errno));
+ return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - recv error");
+ }
+
+ buf[nread] = '\0';
+ // Check for EOF on the socket
+ if (result == 0 && nread == 0) {
+ // Fabricate an event to pass up
+ qCWarning(B2QT_WIFI, "Received EOF on supplicant socket");
+ return snprintf(buf, buflen, WPA_EVENT_TERMINATING " - signal 0 received");
+ }
+
+ /*
+ * Events strings are in the format
+ *
+ * IFNAME=iface <N>CTRL-EVENT-XXX
+ * or
+ * <N>CTRL-EVENT-XXX
+ *
+ * where N is the message level in numerical form (0=VERBOSE, 1=DEBUG,
+ * etc.) and XXX is the event name. The level information is not useful
+ * to us, so strip it off.
+ */
+
+ if (strncmp(buf, "IFNAME=", (sizeof("IFNAME=") - 1)) == 0) {
+ match = strchr(buf, ' ');
+ if (match != NULL) {
+ if (match[1] == '<') {
+ match2 = strchr(match + 2, '>');
+ if (match2 != NULL) {
+ nread -= (match2 - match);
+ memmove(match + 1, match2 + 1, nread - (match - buf) + 1);
+ }
+ }
+ } else {
+ return snprintf(buf, buflen, "%s", "CTRL-EVENT-IGNORE ");
+ }
+ } else if (buf[0] == '<') {
+ match = strchr(buf, '>');
+ if (match != NULL) {
+ nread -= (match + 1 - buf);
+ memmove(buf, match + 1, nread + 1);
+ //qCWarning(B2QT_WIFI, "supplicant generated event without interface - %s", buf);
+ }
+ } else {
+ // let the event go as is!
+ qCWarning(B2QT_WIFI, "supplicant generated event without interface and without message level - %s", buf);
+ }
+
+ return nread;
+}
+
+bool QWifiSupplicant::sendCommand(const QString &command, QByteArray *reply)
+{
+ QByteArray cmd = command.toLocal8Bit();
+ qCDebug(B2QT_WIFI) << "[command]: " << cmd;
+
+ if (ctrl_conn == NULL) {
+ qCWarning(B2QT_WIFI, "Not connected to wpa_supplicant");
+ return false;
+ }
+
+ char data[8192];
+ size_t len = sizeof(data) - 1; // -1: room to add a 0-terminator
+ int ret = wpa_ctrl_request(ctrl_conn, cmd, strlen(cmd), data, &len, NULL);
+ if (ret == -2) {
+ qCWarning(B2QT_WIFI) << "command timed out";
+ // unblocks the monitor receive socket for termination
+ TEMP_FAILURE_RETRY(write(exit_sockets[0], "T", 1));
+ return false;
+ } else if (ret < 0 || strncmp(data, "FAIL", 4) == 0) {
+ return false;
+ }
+
+ if (len == sizeof(data) - 1) {
+ qCWarning(B2QT_WIFI) << "possible buffer overflow detected";
+ return false;
+ }
+
+ data[len] = 0;
+ qCDebug(B2QT_WIFI_VERBOSE) << "[response]: " << data;
+ *reply = QByteArray(data, len);
+
+ return true;
+}
+
+int QWifiSupplicant::receiveEvent(char *reply, size_t *reply_len)
+{
+ int res = 0;
+ int ctrlfd = wpa_ctrl_get_fd(monitor_conn);
+ struct pollfd rfds[2];
+
+ memset(rfds, 0, 2 * sizeof(struct pollfd));
+ rfds[0].fd = ctrlfd;
+ rfds[0].events |= POLLIN;
+ rfds[1].fd = exit_sockets[1];
+ rfds[1].events |= POLLIN;
+ res = TEMP_FAILURE_RETRY(poll(rfds, 2, -1));
+ if (res < 0) {
+ qCWarning(B2QT_WIFI, "Error poll = %d", res);
+ return res;
+ }
+ if (rfds[0].revents & POLLIN) {
+ return wpa_ctrl_recv(monitor_conn, reply, reply_len);
+ }
+
+ /* it is not rfds[0], then it must be rfts[1] (i.e. the exit socket)
+ * or we timed out. In either case, this call has failed ..
+ */
+ return -2;
+}
+
+static inline int hex2num(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ if (c >= 'A' && c <= 'F')
+ return c - 'A' + 10;
+ return -1;
+}
+
+static inline int hex2byte(const char *hex)
+{
+ int a, b;
+ a = hex2num(*hex++);
+ if (a < 0)
+ return -1;
+ b = hex2num(*hex++);
+ if (b < 0)
+ return -1;
+ return (a << 4) | b;
+}
+
+static inline int printf_decode(char *buf, int maxlen, const char *str)
+{
+ const char *pos = str;
+ int len = 0;
+ int val;
+
+ while (*pos) {
+ if (len + 1 >= maxlen)
+ break;
+ switch (*pos) {
+ case '\\':
+ pos++;
+ switch (*pos) {
+ case '\\':
+ buf[len++] = '\\';
+ pos++;
+ break;
+ case '"':
+ buf[len++] = '"';
+ pos++;
+ break;
+ case 'n':
+ buf[len++] = '\n';
+ pos++;
+ break;
+ case 'r':
+ buf[len++] = '\r';
+ pos++;
+ break;
+ case 't':
+ buf[len++] = '\t';
+ pos++;
+ break;
+ case 'e':
+ buf[len++] = '\033';
+ pos++;
+ break;
+ case 'x':
+ pos++;
+ val = hex2byte(pos);
+ if (val < 0) {
+ val = hex2num(*pos);
+ if (val < 0)
+ break;
+ buf[len++] = val;
+ pos++;
+ } else {
+ buf[len++] = val;
+ pos += 2;
+ }
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ val = *pos++ - '0';
+ if (*pos >= '0' && *pos <= '7')
+ val = val * 8 + (*pos++ - '0');
+ if (*pos >= '0' && *pos <= '7')
+ val = val * 8 + (*pos++ - '0');
+ buf[len++] = val;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ buf[len++] = *pos++;
+ break;
+ }
+ }
+ if (maxlen > len)
+ buf[len] = '\0';
+
+ return len;
+}
+
+/*! \internal
+ *
+ Decode wpa_supplicant encoded string, see file hostapd/src/utils/common.c
+ in git://w1.fi/hostap.git repository.
+
+ For Ascii encoded string, any octet < 32 or > 127 is encoded as a "\\x"
+ followed by the hex representation of the octet. Exception chars are ",
+ \\, \\e, \\n, \\r, \\t which are escaped by a backslash
+
+ */
+QString QWifiSupplicant::decodeSsid(const QString &encoded)
+{
+ static char ssid[2048];
+ printf_decode(ssid, sizeof(ssid), encoded.toLatin1().constData());
+ return QString::fromUtf8(ssid);
+}
+
+QT_END_NAMESPACE