/** * @file net_backend_wpa_supplicant.c * @author Ambroz Bizjak * * @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 #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_LINE_LEN 512 #define EVENT_STRING_CONNECTED "CTRL-EVENT-CONNECTED" #define EVENT_STRING_DISCONNECTED "CTRL-EVENT-DISCONNECTED" #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__) struct instance { NCDModuleInst *i; const char *ifname; size_t ifname_len; const char *conf; size_t conf_len; const char *exec; size_t exec_len; 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, 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_AppendNoNull(c, NCDVal_StringData(arg), NCDVal_StringLength(arg))) { goto fail1; } } // append interface name if (!CmdLine_Append(c, "-i") || !CmdLine_AppendNoNull(c, o->ifname, o->ifname_len)) { goto fail1; } // append config file if (!CmdLine_Append(c, "-c") || !CmdLine_AppendNoNull(c, o->conf, o->conf_len)) { 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, 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_StringData(ifname_arg); o->ifname_len = NCDVal_StringLength(ifname_arg); o->conf = NCDVal_StringData(conf_arg); o->conf_len = NCDVal_StringLength(conf_arg); o->exec = NCDVal_StringData(exec_arg); o->exec_len = NCDVal_StringLength(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 };