badvpn/ncd/modules/net_backend_wpa_supplicant.c
2014-11-25 22:30:11 +01:00

567 lines
16 KiB
C

/**
* @file net_backend_wpa_supplicant.c
* @author Ambroz Bizjak <ambrop7@gmail.com>
*
* @section LICENSE
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @section DESCRIPTION
*
* Wireless interface module which runs wpa_supplicant to connect to a wireless network.
*
* Note: wpa_supplicant does not monitor the state of rfkill switches and will fail to
* start if the switch is of when it is started, and will stop working indefinitely if the
* switch is turned off while it is running. Therefore, you should put a "net.backend.rfkill"
* statement in front of the wpa_supplicant statement.
*
* Synopsis: net.backend.wpa_supplicant(string ifname, string conf, string exec, list(string) args)
* Variables:
* bssid - BSSID of the wireless network we connected to, or "none".
* Consists of 6 capital, two-character hexadecimal numbers, separated with colons.
* Example: "01:B2:C3:04:E5:F6"
* ssid - SSID of the wireless network we connected to. Note that this is after what
* wpa_supplicant does to it before it prints it. In particular, it replaces all bytes
* outside [32, 126] with underscores.
*/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <inttypes.h>
#include <misc/cmdline.h>
#include <misc/string_begins_with.h>
#include <misc/stdbuf_cmdline.h>
#include <misc/balloc.h>
#include <misc/find_program.h>
#include <flow/LineBuffer.h>
#include <system/BInputProcess.h>
#include <ncd/module_common.h>
#include <generated/blog_channel_ncd_net_backend_wpa_supplicant.h>
#define MAX_LINE_LEN 512
#define EVENT_STRING_CONNECTED "CTRL-EVENT-CONNECTED"
#define EVENT_STRING_DISCONNECTED "CTRL-EVENT-DISCONNECTED"
struct instance {
NCDModuleInst *i;
MemRef ifname;
MemRef conf;
MemRef exec;
NCDValRef args;
int dying;
int up;
BInputProcess process;
int have_pipe;
LineBuffer pipe_buffer;
PacketPassInterface pipe_input;
int have_info;
int info_have_bssid;
uint8_t info_bssid[6];
char *info_ssid;
};
static int parse_hex_digit (uint8_t d, uint8_t *out);
static int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len);
static int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len);
static int build_cmdline (struct instance *o, CmdLine *c);
static int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len);
static void free_info (struct instance *o);
static void process_error (struct instance *o);
static void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status);
static void process_handler_closed (struct instance *o, int is_error);
static void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len);
static void instance_free (struct instance *o, int is_error);
int parse_hex_digit (uint8_t d, uint8_t *out)
{
switch (d) {
case '0': *out = 0; return 1;
case '1': *out = 1; return 1;
case '2': *out = 2; return 1;
case '3': *out = 3; return 1;
case '4': *out = 4; return 1;
case '5': *out = 5; return 1;
case '6': *out = 6; return 1;
case '7': *out = 7; return 1;
case '8': *out = 8; return 1;
case '9': *out = 9; return 1;
case 'A': case 'a': *out = 10; return 1;
case 'B': case 'b': *out = 11; return 1;
case 'C': case 'c': *out = 12; return 1;
case 'D': case 'd': *out = 13; return 1;
case 'E': case 'e': *out = 14; return 1;
case 'F': case 'f': *out = 15; return 1;
}
return 0;
}
int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len)
{
// Trying to associate with AB:CD:EF:01:23:45 (SSID='Some SSID' freq=2462 MHz)
int p;
if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with "))) {
return 0;
}
data += p;
data_len -= p;
for (int i = 0; i < 6; i++) {
uint8_t d1;
uint8_t d2;
if (data_len < 2 || !parse_hex_digit(data[0], &d1) || !parse_hex_digit(data[1], &d2)) {
return 0;
}
data += 2;
data_len -= 2;
out_bssid[i] = ((d1 << 4) | d2);
if (i != 5) {
if (data_len < 1 || data[0] != ':') {
return 0;
}
data += 1;
data_len -= 1;
}
}
if (!(p = data_begins_with((char *)data, data_len, " (SSID='"))) {
return 0;
}
data += p;
data_len -= p;
// find last '
uint8_t *q = NULL;
for (int i = data_len; i > 0; i--) {
if (data[i - 1] == '\'') {
q = &data[i - 1];
break;
}
}
if (!q) {
return 0;
}
*out_ssid = data;
*out_ssid_len = q - data;
return 1;
}
int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len)
{
// Trying to associate with SSID 'Some SSID'
int p;
if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with SSID '"))) {
return 0;
}
data += p;
data_len -= p;
// find last '
uint8_t *q = NULL;
for (int i = data_len; i > 0; i--) {
if (data[i - 1] == '\'') {
q = &data[i - 1];
break;
}
}
if (!q) {
return 0;
}
*out_ssid = data;
*out_ssid_len = q - data;
return 1;
}
int build_cmdline (struct instance *o, CmdLine *c)
{
// init cmdline
if (!CmdLine_Init(c)) {
goto fail0;
}
// find stdbuf executable
char *stdbuf_exec = badvpn_find_program("stdbuf");
if (!stdbuf_exec) {
ModuleLog(o->i, BLOG_ERROR, "cannot find stdbuf executable");
goto fail1;
}
// append stdbuf part
int res = build_stdbuf_cmdline(c, stdbuf_exec, o->exec.ptr, o->exec.len);
free(stdbuf_exec);
if (!res) {
goto fail1;
}
// append user arguments
size_t count = NCDVal_ListCount(o->args);
for (size_t j = 0; j < count; j++) {
NCDValRef arg = NCDVal_ListGet(o->args, j);
if (!NCDVal_IsStringNoNulls(arg)) {
ModuleLog(o->i, BLOG_ERROR, "wrong type");
goto fail1;
}
// append argument
if (!CmdLine_AppendNoNullMr(c, NCDVal_StringMemRef(arg))) {
goto fail1;
}
}
// append interface name
if (!CmdLine_Append(c, "-i") || !CmdLine_AppendNoNullMr(c, o->ifname)) {
goto fail1;
}
// append config file
if (!CmdLine_Append(c, "-c") || !CmdLine_AppendNoNullMr(c, o->conf)) {
goto fail1;
}
// terminate cmdline
if (!CmdLine_Finish(c)) {
goto fail1;
}
return 1;
fail1:
CmdLine_Free(c);
fail0:
return 0;
}
int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len)
{
ASSERT(!o->have_info)
// set bssid
o->info_have_bssid = have_bssid;
if (have_bssid) {
memcpy(o->info_bssid, bssid, 6);
}
// set ssid
if (!(o->info_ssid = BAllocSize(bsize_add(bsize_fromsize(ssid_len), bsize_fromsize(1))))) {
ModuleLog(o->i, BLOG_ERROR, "BAllocSize failed");
return 0;
}
memcpy(o->info_ssid, ssid, ssid_len);
o->info_ssid[ssid_len] = '\0';
// set have info
o->have_info = 1;
return 1;
}
void free_info (struct instance *o)
{
ASSERT(o->have_info)
// free ssid
BFree(o->info_ssid);
// set not have info
o->have_info = 0;
}
void process_error (struct instance *o)
{
BInputProcess_Terminate(&o->process);
}
void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status)
{
ModuleLog(o->i, (o->dying ? BLOG_INFO : BLOG_ERROR), "process terminated");
// die
instance_free(o, !o->dying);
return;
}
void process_handler_closed (struct instance *o, int is_error)
{
ASSERT(o->have_pipe)
if (is_error) {
ModuleLog(o->i, BLOG_ERROR, "pipe error");
} else {
ModuleLog(o->i, BLOG_INFO, "pipe closed");
}
// free buffer
LineBuffer_Free(&o->pipe_buffer);
// free input interface
PacketPassInterface_Free(&o->pipe_input);
// set have no pipe
o->have_pipe = 0;
}
void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len)
{
ASSERT(o->have_pipe)
ASSERT(data_len > 0)
// accept packet
PacketPassInterface_Done(&o->pipe_input);
if (o->dying) {
return;
}
// strip "interface: " from beginning of line. Older wpa_supplicant versions (<1.0) don't add this
// prefix, so don't fail if there isn't one.
size_t l1;
size_t l2;
if (o->ifname.len > 0 && (l1 = data_begins_with_bin((char *)data, data_len, o->ifname.ptr, o->ifname.len)) && (l2 = data_begins_with((char *)data + l1, data_len - l1, ": "))) {
data += l1 + l2;
data_len -= l1 + l2;
}
int have_bssid = 1;
uint8_t bssid[6];
uint8_t *ssid;
int ssid_len;
if (parse_trying(data, data_len, bssid, &ssid, &ssid_len) || (have_bssid = 0, parse_trying_nobssid(data, data_len, &ssid, &ssid_len))) {
ModuleLog(o->i, BLOG_INFO, "trying event");
if (o->up) {
ModuleLog(o->i, BLOG_ERROR, "trying unexpected!");
process_error(o);
return;
}
if (o->have_info) {
free_info(o);
}
if (!init_info(o, have_bssid, bssid, ssid, ssid_len)) {
ModuleLog(o->i, BLOG_ERROR, "init_info failed");
process_error(o);
return;
}
}
else if (data_begins_with((char *)data, data_len, EVENT_STRING_CONNECTED)) {
ModuleLog(o->i, BLOG_INFO, "connected event");
if (o->up || !o->have_info) {
ModuleLog(o->i, BLOG_ERROR, "connected unexpected!");
process_error(o);
return;
}
o->up = 1;
NCDModuleInst_Backend_Up(o->i);
}
else if (data_begins_with((char *)data, data_len, EVENT_STRING_DISCONNECTED)) {
ModuleLog(o->i, BLOG_INFO, "disconnected event");
if (o->have_info) {
free_info(o);
}
if (o->up) {
o->up = 0;
NCDModuleInst_Backend_Down(o->i);
}
}
}
static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
{
struct instance *o = vo;
o->i = i;
// read arguments
NCDValRef ifname_arg;
NCDValRef conf_arg;
NCDValRef exec_arg;
NCDValRef args_arg;
if (!NCDVal_ListRead(params->args, 4, &ifname_arg, &conf_arg, &exec_arg, &args_arg)) {
ModuleLog(o->i, BLOG_ERROR, "wrong arity");
goto fail0;
}
if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsStringNoNulls(conf_arg) ||
!NCDVal_IsStringNoNulls(exec_arg) || !NCDVal_IsList(args_arg)) {
ModuleLog(o->i, BLOG_ERROR, "wrong type");
goto fail0;
}
o->ifname = NCDVal_StringMemRef(ifname_arg);
o->conf = NCDVal_StringMemRef(conf_arg);
o->exec = NCDVal_StringMemRef(exec_arg);
o->args = args_arg;
// set not dying
o->dying = 0;
// set not up
o->up = 0;
// build process cmdline
CmdLine c;
if (!build_cmdline(o, &c)) {
ModuleLog(o->i, BLOG_ERROR, "failed to build cmdline");
goto fail0;
}
// init process
if (!BInputProcess_Init(&o->process, o->i->params->iparams->reactor, o->i->params->iparams->manager, o,
(BInputProcess_handler_terminated)process_handler_terminated,
(BInputProcess_handler_closed)process_handler_closed
)) {
ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Init failed");
goto fail1;
}
// init input interface
PacketPassInterface_Init(&o->pipe_input, MAX_LINE_LEN, (PacketPassInterface_handler_send)process_pipe_handler_send, o, BReactor_PendingGroup(o->i->params->iparams->reactor));
// init buffer
if (!LineBuffer_Init(&o->pipe_buffer, BInputProcess_GetInput(&o->process), &o->pipe_input, MAX_LINE_LEN, '\n')) {
ModuleLog(o->i, BLOG_ERROR, "LineBuffer_Init failed");
goto fail2;
}
// set have pipe
o->have_pipe = 1;
// start process
if (!BInputProcess_Start(&o->process, ((char **)c.arr.v)[0], (char **)c.arr.v, NULL)) {
ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Start failed");
goto fail3;
}
// set not have info
o->have_info = 0;
CmdLine_Free(&c);
return;
fail3:
LineBuffer_Free(&o->pipe_buffer);
fail2:
PacketPassInterface_Free(&o->pipe_input);
BInputProcess_Free(&o->process);
fail1:
CmdLine_Free(&c);
fail0:
NCDModuleInst_Backend_DeadError(i);
}
void instance_free (struct instance *o, int is_error)
{
// free info
if (o->have_info) {
free_info(o);
}
if (o->have_pipe) {
// free buffer
LineBuffer_Free(&o->pipe_buffer);
// free input interface
PacketPassInterface_Free(&o->pipe_input);
}
// free process
BInputProcess_Free(&o->process);
if (is_error) {
NCDModuleInst_Backend_DeadError(o->i);
} else {
NCDModuleInst_Backend_Dead(o->i);
}
}
static void func_die (void *vo)
{
struct instance *o = vo;
ASSERT(!o->dying)
// request termination
BInputProcess_Terminate(&o->process);
// remember dying
o->dying = 1;
}
static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
{
struct instance *o = vo;
ASSERT(o->up)
ASSERT(o->have_info)
if (!strcmp(name, "bssid")) {
char str[18];
if (!o->info_have_bssid) {
sprintf(str, "none");
} else {
uint8_t *id = o->info_bssid;
sprintf(str, "%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8":%02"PRIX8, id[0], id[1], id[2], id[3], id[4], id[5]);
}
*out = NCDVal_NewString(mem, str);
return 1;
}
if (!strcmp(name, "ssid")) {
*out = NCDVal_NewString(mem, o->info_ssid);
return 1;
}
return 0;
}
static struct NCDModule modules[] = {
{
.type = "net.backend.wpa_supplicant",
.func_new2 = func_new,
.func_die = func_die,
.func_getvar = func_getvar,
.alloc_size = sizeof(struct instance)
}, {
.type = NULL
}
};
const struct NCDModuleGroup ncdmodule_net_backend_wpa_supplicant = {
.modules = modules
};