net_backend_wpa_supplicant.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. /**
  2. * @file net_backend_wpa_supplicant.c
  3. * @author Ambroz Bizjak <ambrop7@gmail.com>
  4. *
  5. * @section LICENSE
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the name of the author nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * @section DESCRIPTION
  30. *
  31. * Wireless interface module which runs wpa_supplicant to connect to a wireless network.
  32. *
  33. * Note: wpa_supplicant does not monitor the state of rfkill switches and will fail to
  34. * start if the switch is of when it is started, and will stop working indefinitely if the
  35. * switch is turned off while it is running. Therefore, you should put a "net.backend.rfkill"
  36. * statement in front of the wpa_supplicant statement.
  37. *
  38. * Synopsis: net.backend.wpa_supplicant(string ifname, string conf, string exec, list(string) args)
  39. * Variables:
  40. * bssid - BSSID of the wireless network we connected to, or "none".
  41. * Consists of 6 capital, two-character hexadecimal numbers, separated with colons.
  42. * Example: "01:B2:C3:04:E5:F6"
  43. * ssid - SSID of the wireless network we connected to. Note that this is after what
  44. * wpa_supplicant does to it before it prints it. In particular, it replaces all bytes
  45. * outside [32, 126] with underscores.
  46. */
  47. #include <stdlib.h>
  48. #include <string.h>
  49. #include <stdio.h>
  50. #include <inttypes.h>
  51. #include <misc/cmdline.h>
  52. #include <misc/string_begins_with.h>
  53. #include <misc/stdbuf_cmdline.h>
  54. #include <misc/balloc.h>
  55. #include <misc/find_program.h>
  56. #include <flow/LineBuffer.h>
  57. #include <system/BInputProcess.h>
  58. #include <ncd/NCDModule.h>
  59. #include <generated/blog_channel_ncd_net_backend_wpa_supplicant.h>
  60. #define MAX_LINE_LEN 512
  61. #define EVENT_STRING_CONNECTED "CTRL-EVENT-CONNECTED"
  62. #define EVENT_STRING_DISCONNECTED "CTRL-EVENT-DISCONNECTED"
  63. #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
  64. struct instance {
  65. NCDModuleInst *i;
  66. const char *ifname;
  67. size_t ifname_len;
  68. const char *conf;
  69. size_t conf_len;
  70. const char *exec;
  71. size_t exec_len;
  72. NCDValRef args;
  73. int dying;
  74. int up;
  75. BInputProcess process;
  76. int have_pipe;
  77. LineBuffer pipe_buffer;
  78. PacketPassInterface pipe_input;
  79. int have_info;
  80. int info_have_bssid;
  81. uint8_t info_bssid[6];
  82. char *info_ssid;
  83. };
  84. static int parse_hex_digit (uint8_t d, uint8_t *out);
  85. static int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len);
  86. static int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len);
  87. static int build_cmdline (struct instance *o, CmdLine *c);
  88. static int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len);
  89. static void free_info (struct instance *o);
  90. static void process_error (struct instance *o);
  91. static void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status);
  92. static void process_handler_closed (struct instance *o, int is_error);
  93. static void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len);
  94. static void instance_free (struct instance *o, int is_error);
  95. int parse_hex_digit (uint8_t d, uint8_t *out)
  96. {
  97. switch (d) {
  98. case '0': *out = 0; return 1;
  99. case '1': *out = 1; return 1;
  100. case '2': *out = 2; return 1;
  101. case '3': *out = 3; return 1;
  102. case '4': *out = 4; return 1;
  103. case '5': *out = 5; return 1;
  104. case '6': *out = 6; return 1;
  105. case '7': *out = 7; return 1;
  106. case '8': *out = 8; return 1;
  107. case '9': *out = 9; return 1;
  108. case 'A': case 'a': *out = 10; return 1;
  109. case 'B': case 'b': *out = 11; return 1;
  110. case 'C': case 'c': *out = 12; return 1;
  111. case 'D': case 'd': *out = 13; return 1;
  112. case 'E': case 'e': *out = 14; return 1;
  113. case 'F': case 'f': *out = 15; return 1;
  114. }
  115. return 0;
  116. }
  117. int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len)
  118. {
  119. // Trying to associate with AB:CD:EF:01:23:45 (SSID='Some SSID' freq=2462 MHz)
  120. int p;
  121. if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with "))) {
  122. return 0;
  123. }
  124. data += p;
  125. data_len -= p;
  126. for (int i = 0; i < 6; i++) {
  127. uint8_t d1;
  128. uint8_t d2;
  129. if (data_len < 2 || !parse_hex_digit(data[0], &d1) || !parse_hex_digit(data[1], &d2)) {
  130. return 0;
  131. }
  132. data += 2;
  133. data_len -= 2;
  134. out_bssid[i] = ((d1 << 4) | d2);
  135. if (i != 5) {
  136. if (data_len < 1 || data[0] != ':') {
  137. return 0;
  138. }
  139. data += 1;
  140. data_len -= 1;
  141. }
  142. }
  143. if (!(p = data_begins_with((char *)data, data_len, " (SSID='"))) {
  144. return 0;
  145. }
  146. data += p;
  147. data_len -= p;
  148. // find last '
  149. uint8_t *q = NULL;
  150. for (int i = data_len; i > 0; i--) {
  151. if (data[i - 1] == '\'') {
  152. q = &data[i - 1];
  153. break;
  154. }
  155. }
  156. if (!q) {
  157. return 0;
  158. }
  159. *out_ssid = data;
  160. *out_ssid_len = q - data;
  161. return 1;
  162. }
  163. int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len)
  164. {
  165. // Trying to associate with SSID 'Some SSID'
  166. int p;
  167. if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with SSID '"))) {
  168. return 0;
  169. }
  170. data += p;
  171. data_len -= p;
  172. // find last '
  173. uint8_t *q = NULL;
  174. for (int i = data_len; i > 0; i--) {
  175. if (data[i - 1] == '\'') {
  176. q = &data[i - 1];
  177. break;
  178. }
  179. }
  180. if (!q) {
  181. return 0;
  182. }
  183. *out_ssid = data;
  184. *out_ssid_len = q - data;
  185. return 1;
  186. }
  187. int build_cmdline (struct instance *o, CmdLine *c)
  188. {
  189. // init cmdline
  190. if (!CmdLine_Init(c)) {
  191. goto fail0;
  192. }
  193. // find stdbuf executable
  194. char *stdbuf_exec = badvpn_find_program("stdbuf");
  195. if (!stdbuf_exec) {
  196. ModuleLog(o->i, BLOG_ERROR, "cannot find stdbuf executable");
  197. goto fail1;
  198. }
  199. // append stdbuf part
  200. int res = build_stdbuf_cmdline(c, stdbuf_exec, o->exec, o->exec_len);
  201. free(stdbuf_exec);
  202. if (!res) {
  203. goto fail1;
  204. }
  205. // append user arguments
  206. size_t count = NCDVal_ListCount(o->args);
  207. for (size_t j = 0; j < count; j++) {
  208. NCDValRef arg = NCDVal_ListGet(o->args, j);
  209. if (!NCDVal_IsStringNoNulls(arg)) {
  210. ModuleLog(o->i, BLOG_ERROR, "wrong type");
  211. goto fail1;
  212. }
  213. // append argument
  214. if (!CmdLine_AppendNoNull(c, NCDVal_StringData(arg), NCDVal_StringLength(arg))) {
  215. goto fail1;
  216. }
  217. }
  218. // append interface name
  219. if (!CmdLine_Append(c, "-i") || !CmdLine_AppendNoNull(c, o->ifname, o->ifname_len)) {
  220. goto fail1;
  221. }
  222. // append config file
  223. if (!CmdLine_Append(c, "-c") || !CmdLine_AppendNoNull(c, o->conf, o->conf_len)) {
  224. goto fail1;
  225. }
  226. // terminate cmdline
  227. if (!CmdLine_Finish(c)) {
  228. goto fail1;
  229. }
  230. return 1;
  231. fail1:
  232. CmdLine_Free(c);
  233. fail0:
  234. return 0;
  235. }
  236. int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len)
  237. {
  238. ASSERT(!o->have_info)
  239. // set bssid
  240. o->info_have_bssid = have_bssid;
  241. if (have_bssid) {
  242. memcpy(o->info_bssid, bssid, 6);
  243. }
  244. // set ssid
  245. if (!(o->info_ssid = BAllocSize(bsize_add(bsize_fromsize(ssid_len), bsize_fromsize(1))))) {
  246. ModuleLog(o->i, BLOG_ERROR, "BAllocSize failed");
  247. return 0;
  248. }
  249. memcpy(o->info_ssid, ssid, ssid_len);
  250. o->info_ssid[ssid_len] = '\0';
  251. // set have info
  252. o->have_info = 1;
  253. return 1;
  254. }
  255. void free_info (struct instance *o)
  256. {
  257. ASSERT(o->have_info)
  258. // free ssid
  259. BFree(o->info_ssid);
  260. // set not have info
  261. o->have_info = 0;
  262. }
  263. void process_error (struct instance *o)
  264. {
  265. BInputProcess_Terminate(&o->process);
  266. }
  267. void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status)
  268. {
  269. ModuleLog(o->i, (o->dying ? BLOG_INFO : BLOG_ERROR), "process terminated");
  270. // die
  271. instance_free(o, !o->dying);
  272. return;
  273. }
  274. void process_handler_closed (struct instance *o, int is_error)
  275. {
  276. ASSERT(o->have_pipe)
  277. if (is_error) {
  278. ModuleLog(o->i, BLOG_ERROR, "pipe error");
  279. } else {
  280. ModuleLog(o->i, BLOG_INFO, "pipe closed");
  281. }
  282. // free buffer
  283. LineBuffer_Free(&o->pipe_buffer);
  284. // free input interface
  285. PacketPassInterface_Free(&o->pipe_input);
  286. // set have no pipe
  287. o->have_pipe = 0;
  288. }
  289. void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len)
  290. {
  291. ASSERT(o->have_pipe)
  292. ASSERT(data_len > 0)
  293. // accept packet
  294. PacketPassInterface_Done(&o->pipe_input);
  295. if (o->dying) {
  296. return;
  297. }
  298. // strip "interface: " from beginning of line. Older wpa_supplicant versions (<1.0) don't add this
  299. // prefix, so don't fail if there isn't one.
  300. size_t l1;
  301. size_t l2;
  302. 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, ": "))) {
  303. data += l1 + l2;
  304. data_len -= l1 + l2;
  305. }
  306. int have_bssid = 1;
  307. uint8_t bssid[6];
  308. uint8_t *ssid;
  309. int ssid_len;
  310. if (parse_trying(data, data_len, bssid, &ssid, &ssid_len) || (have_bssid = 0, parse_trying_nobssid(data, data_len, &ssid, &ssid_len))) {
  311. ModuleLog(o->i, BLOG_INFO, "trying event");
  312. if (o->up) {
  313. ModuleLog(o->i, BLOG_ERROR, "trying unexpected!");
  314. process_error(o);
  315. return;
  316. }
  317. if (o->have_info) {
  318. free_info(o);
  319. }
  320. if (!init_info(o, have_bssid, bssid, ssid, ssid_len)) {
  321. ModuleLog(o->i, BLOG_ERROR, "init_info failed");
  322. process_error(o);
  323. return;
  324. }
  325. }
  326. else if (data_begins_with((char *)data, data_len, EVENT_STRING_CONNECTED)) {
  327. ModuleLog(o->i, BLOG_INFO, "connected event");
  328. if (o->up || !o->have_info) {
  329. ModuleLog(o->i, BLOG_ERROR, "connected unexpected!");
  330. process_error(o);
  331. return;
  332. }
  333. o->up = 1;
  334. NCDModuleInst_Backend_Up(o->i);
  335. }
  336. else if (data_begins_with((char *)data, data_len, EVENT_STRING_DISCONNECTED)) {
  337. ModuleLog(o->i, BLOG_INFO, "disconnected event");
  338. if (o->have_info) {
  339. free_info(o);
  340. }
  341. if (o->up) {
  342. o->up = 0;
  343. NCDModuleInst_Backend_Down(o->i);
  344. }
  345. }
  346. }
  347. static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  348. {
  349. struct instance *o = vo;
  350. o->i = i;
  351. // read arguments
  352. NCDValRef ifname_arg;
  353. NCDValRef conf_arg;
  354. NCDValRef exec_arg;
  355. NCDValRef args_arg;
  356. if (!NCDVal_ListRead(params->args, 4, &ifname_arg, &conf_arg, &exec_arg, &args_arg)) {
  357. ModuleLog(o->i, BLOG_ERROR, "wrong arity");
  358. goto fail0;
  359. }
  360. if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsStringNoNulls(conf_arg) ||
  361. !NCDVal_IsStringNoNulls(exec_arg) || !NCDVal_IsList(args_arg)) {
  362. ModuleLog(o->i, BLOG_ERROR, "wrong type");
  363. goto fail0;
  364. }
  365. o->ifname = NCDVal_StringData(ifname_arg);
  366. o->ifname_len = NCDVal_StringLength(ifname_arg);
  367. o->conf = NCDVal_StringData(conf_arg);
  368. o->conf_len = NCDVal_StringLength(conf_arg);
  369. o->exec = NCDVal_StringData(exec_arg);
  370. o->exec_len = NCDVal_StringLength(exec_arg);
  371. o->args = args_arg;
  372. // set not dying
  373. o->dying = 0;
  374. // set not up
  375. o->up = 0;
  376. // build process cmdline
  377. CmdLine c;
  378. if (!build_cmdline(o, &c)) {
  379. ModuleLog(o->i, BLOG_ERROR, "failed to build cmdline");
  380. goto fail0;
  381. }
  382. // init process
  383. if (!BInputProcess_Init(&o->process, o->i->params->iparams->reactor, o->i->params->iparams->manager, o,
  384. (BInputProcess_handler_terminated)process_handler_terminated,
  385. (BInputProcess_handler_closed)process_handler_closed
  386. )) {
  387. ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Init failed");
  388. goto fail1;
  389. }
  390. // init input interface
  391. PacketPassInterface_Init(&o->pipe_input, MAX_LINE_LEN, (PacketPassInterface_handler_send)process_pipe_handler_send, o, BReactor_PendingGroup(o->i->params->iparams->reactor));
  392. // init buffer
  393. if (!LineBuffer_Init(&o->pipe_buffer, BInputProcess_GetInput(&o->process), &o->pipe_input, MAX_LINE_LEN, '\n')) {
  394. ModuleLog(o->i, BLOG_ERROR, "LineBuffer_Init failed");
  395. goto fail2;
  396. }
  397. // set have pipe
  398. o->have_pipe = 1;
  399. // start process
  400. if (!BInputProcess_Start(&o->process, ((char **)c.arr.v)[0], (char **)c.arr.v, NULL)) {
  401. ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Start failed");
  402. goto fail3;
  403. }
  404. // set not have info
  405. o->have_info = 0;
  406. CmdLine_Free(&c);
  407. return;
  408. fail3:
  409. LineBuffer_Free(&o->pipe_buffer);
  410. fail2:
  411. PacketPassInterface_Free(&o->pipe_input);
  412. BInputProcess_Free(&o->process);
  413. fail1:
  414. CmdLine_Free(&c);
  415. fail0:
  416. NCDModuleInst_Backend_DeadError(i);
  417. }
  418. void instance_free (struct instance *o, int is_error)
  419. {
  420. // free info
  421. if (o->have_info) {
  422. free_info(o);
  423. }
  424. if (o->have_pipe) {
  425. // free buffer
  426. LineBuffer_Free(&o->pipe_buffer);
  427. // free input interface
  428. PacketPassInterface_Free(&o->pipe_input);
  429. }
  430. // free process
  431. BInputProcess_Free(&o->process);
  432. if (is_error) {
  433. NCDModuleInst_Backend_DeadError(o->i);
  434. } else {
  435. NCDModuleInst_Backend_Dead(o->i);
  436. }
  437. }
  438. static void func_die (void *vo)
  439. {
  440. struct instance *o = vo;
  441. ASSERT(!o->dying)
  442. // request termination
  443. BInputProcess_Terminate(&o->process);
  444. // remember dying
  445. o->dying = 1;
  446. }
  447. static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out)
  448. {
  449. struct instance *o = vo;
  450. ASSERT(o->up)
  451. ASSERT(o->have_info)
  452. if (!strcmp(name, "bssid")) {
  453. char str[18];
  454. if (!o->info_have_bssid) {
  455. sprintf(str, "none");
  456. } else {
  457. uint8_t *id = o->info_bssid;
  458. 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]);
  459. }
  460. *out = NCDVal_NewString(mem, str);
  461. return 1;
  462. }
  463. if (!strcmp(name, "ssid")) {
  464. *out = NCDVal_NewString(mem, o->info_ssid);
  465. return 1;
  466. }
  467. return 0;
  468. }
  469. static struct NCDModule modules[] = {
  470. {
  471. .type = "net.backend.wpa_supplicant",
  472. .func_new2 = func_new,
  473. .func_die = func_die,
  474. .func_getvar = func_getvar,
  475. .alloc_size = sizeof(struct instance)
  476. }, {
  477. .type = NULL
  478. }
  479. };
  480. const struct NCDModuleGroup ncdmodule_net_backend_wpa_supplicant = {
  481. .modules = modules
  482. };