diff options
Diffstat (limited to 'src/qtdevicesettings/networksettingsplugin/networksettings/wpasupplicant/qwifisupplicant.cpp')
-rw-r--r-- | src/qtdevicesettings/networksettingsplugin/networksettings/wpasupplicant/qwifisupplicant.cpp | 443 |
1 files changed, 443 insertions, 0 deletions
diff --git a/src/qtdevicesettings/networksettingsplugin/networksettings/wpasupplicant/qwifisupplicant.cpp b/src/qtdevicesettings/networksettingsplugin/networksettings/wpasupplicant/qwifisupplicant.cpp new file mode 100644 index 0000000..779475e --- /dev/null +++ b/src/qtdevicesettings/networksettingsplugin/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 |