BadVPN – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 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 | |||
48 | #include <stdlib.h> |
||
49 | #include <string.h> |
||
50 | #include <stdio.h> |
||
51 | #include <inttypes.h> |
||
52 | |||
53 | #include <misc/cmdline.h> |
||
54 | #include <misc/string_begins_with.h> |
||
55 | #include <misc/stdbuf_cmdline.h> |
||
56 | #include <misc/balloc.h> |
||
57 | #include <misc/find_program.h> |
||
58 | #include <flow/LineBuffer.h> |
||
59 | #include <system/BInputProcess.h> |
||
60 | |||
61 | #include <ncd/module_common.h> |
||
62 | |||
63 | #include <generated/blog_channel_ncd_net_backend_wpa_supplicant.h> |
||
64 | |||
65 | #define MAX_LINE_LEN 512 |
||
66 | #define EVENT_STRING_CONNECTED "CTRL-EVENT-CONNECTED" |
||
67 | #define EVENT_STRING_DISCONNECTED "CTRL-EVENT-DISCONNECTED" |
||
68 | |||
69 | struct instance { |
||
70 | NCDModuleInst *i; |
||
71 | MemRef ifname; |
||
72 | MemRef conf; |
||
73 | MemRef exec; |
||
74 | NCDValRef args; |
||
75 | int dying; |
||
76 | int up; |
||
77 | BInputProcess process; |
||
78 | int have_pipe; |
||
79 | LineBuffer pipe_buffer; |
||
80 | PacketPassInterface pipe_input; |
||
81 | int have_info; |
||
82 | int info_have_bssid; |
||
83 | uint8_t info_bssid[6]; |
||
84 | char *info_ssid; |
||
85 | }; |
||
86 | |||
87 | static int parse_hex_digit (uint8_t d, uint8_t *out); |
||
88 | static int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len); |
||
89 | static int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len); |
||
90 | static int build_cmdline (struct instance *o, CmdLine *c); |
||
91 | static int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len); |
||
92 | static void free_info (struct instance *o); |
||
93 | static void process_error (struct instance *o); |
||
94 | static void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status); |
||
95 | static void process_handler_closed (struct instance *o, int is_error); |
||
96 | static void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len); |
||
97 | static void instance_free (struct instance *o, int is_error); |
||
98 | |||
99 | int parse_hex_digit (uint8_t d, uint8_t *out) |
||
100 | { |
||
101 | switch (d) { |
||
102 | case '0': *out = 0; return 1; |
||
103 | case '1': *out = 1; return 1; |
||
104 | case '2': *out = 2; return 1; |
||
105 | case '3': *out = 3; return 1; |
||
106 | case '4': *out = 4; return 1; |
||
107 | case '5': *out = 5; return 1; |
||
108 | case '6': *out = 6; return 1; |
||
109 | case '7': *out = 7; return 1; |
||
110 | case '8': *out = 8; return 1; |
||
111 | case '9': *out = 9; return 1; |
||
112 | case 'A': case 'a': *out = 10; return 1; |
||
113 | case 'B': case 'b': *out = 11; return 1; |
||
114 | case 'C': case 'c': *out = 12; return 1; |
||
115 | case 'D': case 'd': *out = 13; return 1; |
||
116 | case 'E': case 'e': *out = 14; return 1; |
||
117 | case 'F': case 'f': *out = 15; return 1; |
||
118 | } |
||
119 | |||
120 | return 0; |
||
121 | } |
||
122 | |||
123 | int parse_trying (uint8_t *data, int data_len, uint8_t *out_bssid, uint8_t **out_ssid, int *out_ssid_len) |
||
124 | { |
||
125 | // Trying to associate with AB:CD:EF:01:23:45 (SSID='Some SSID' freq=2462 MHz) |
||
126 | |||
127 | int p; |
||
128 | if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with "))) { |
||
129 | return 0; |
||
130 | } |
||
131 | data += p; |
||
132 | data_len -= p; |
||
133 | |||
134 | for (int i = 0; i < 6; i++) { |
||
135 | uint8_t d1; |
||
136 | uint8_t d2; |
||
137 | if (data_len < 2 || !parse_hex_digit(data[0], &d1) || !parse_hex_digit(data[1], &d2)) { |
||
138 | return 0; |
||
139 | } |
||
140 | data += 2; |
||
141 | data_len -= 2; |
||
142 | out_bssid[i] = ((d1 << 4) | d2); |
||
143 | |||
144 | if (i != 5) { |
||
145 | if (data_len < 1 || data[0] != ':') { |
||
146 | return 0; |
||
147 | } |
||
148 | data += 1; |
||
149 | data_len -= 1; |
||
150 | } |
||
151 | } |
||
152 | |||
153 | if (!(p = data_begins_with((char *)data, data_len, " (SSID='"))) { |
||
154 | return 0; |
||
155 | } |
||
156 | data += p; |
||
157 | data_len -= p; |
||
158 | |||
159 | // find last ' |
||
160 | uint8_t *q = NULL; |
||
161 | for (int i = data_len; i > 0; i--) { |
||
162 | if (data[i - 1] == '\'') { |
||
163 | q = &data[i - 1]; |
||
164 | break; |
||
165 | } |
||
166 | } |
||
167 | if (!q) { |
||
168 | return 0; |
||
169 | } |
||
170 | |||
171 | *out_ssid = data; |
||
172 | *out_ssid_len = q - data; |
||
173 | |||
174 | return 1; |
||
175 | } |
||
176 | |||
177 | int parse_trying_nobssid (uint8_t *data, int data_len, uint8_t **out_ssid, int *out_ssid_len) |
||
178 | { |
||
179 | // Trying to associate with SSID 'Some SSID' |
||
180 | |||
181 | int p; |
||
182 | if (!(p = data_begins_with((char *)data, data_len, "Trying to associate with SSID '"))) { |
||
183 | return 0; |
||
184 | } |
||
185 | data += p; |
||
186 | data_len -= p; |
||
187 | |||
188 | // find last ' |
||
189 | uint8_t *q = NULL; |
||
190 | for (int i = data_len; i > 0; i--) { |
||
191 | if (data[i - 1] == '\'') { |
||
192 | q = &data[i - 1]; |
||
193 | break; |
||
194 | } |
||
195 | } |
||
196 | if (!q) { |
||
197 | return 0; |
||
198 | } |
||
199 | |||
200 | *out_ssid = data; |
||
201 | *out_ssid_len = q - data; |
||
202 | |||
203 | return 1; |
||
204 | } |
||
205 | |||
206 | int build_cmdline (struct instance *o, CmdLine *c) |
||
207 | { |
||
208 | // init cmdline |
||
209 | if (!CmdLine_Init(c)) { |
||
210 | goto fail0; |
||
211 | } |
||
212 | |||
213 | // find stdbuf executable |
||
214 | char *stdbuf_exec = badvpn_find_program("stdbuf"); |
||
215 | if (!stdbuf_exec) { |
||
216 | ModuleLog(o->i, BLOG_ERROR, "cannot find stdbuf executable"); |
||
217 | goto fail1; |
||
218 | } |
||
219 | |||
220 | // append stdbuf part |
||
221 | int res = build_stdbuf_cmdline(c, stdbuf_exec, o->exec.ptr, o->exec.len); |
||
222 | free(stdbuf_exec); |
||
223 | if (!res) { |
||
224 | goto fail1; |
||
225 | } |
||
226 | |||
227 | // append user arguments |
||
228 | size_t count = NCDVal_ListCount(o->args); |
||
229 | for (size_t j = 0; j < count; j++) { |
||
230 | NCDValRef arg = NCDVal_ListGet(o->args, j); |
||
231 | |||
232 | if (!NCDVal_IsStringNoNulls(arg)) { |
||
233 | ModuleLog(o->i, BLOG_ERROR, "wrong type"); |
||
234 | goto fail1; |
||
235 | } |
||
236 | |||
237 | // append argument |
||
238 | if (!CmdLine_AppendNoNullMr(c, NCDVal_StringMemRef(arg))) { |
||
239 | goto fail1; |
||
240 | } |
||
241 | } |
||
242 | |||
243 | // append interface name |
||
244 | if (!CmdLine_Append(c, "-i") || !CmdLine_AppendNoNullMr(c, o->ifname)) { |
||
245 | goto fail1; |
||
246 | } |
||
247 | |||
248 | // append config file |
||
249 | if (!CmdLine_Append(c, "-c") || !CmdLine_AppendNoNullMr(c, o->conf)) { |
||
250 | goto fail1; |
||
251 | } |
||
252 | |||
253 | // terminate cmdline |
||
254 | if (!CmdLine_Finish(c)) { |
||
255 | goto fail1; |
||
256 | } |
||
257 | |||
258 | return 1; |
||
259 | |||
260 | fail1: |
||
261 | CmdLine_Free(c); |
||
262 | fail0: |
||
263 | return 0; |
||
264 | } |
||
265 | |||
266 | int init_info (struct instance *o, int have_bssid, const uint8_t *bssid, const uint8_t *ssid, size_t ssid_len) |
||
267 | { |
||
268 | ASSERT(!o->have_info) |
||
269 | |||
270 | // set bssid |
||
271 | o->info_have_bssid = have_bssid; |
||
272 | if (have_bssid) { |
||
273 | memcpy(o->info_bssid, bssid, 6); |
||
274 | } |
||
275 | |||
276 | // set ssid |
||
277 | if (!(o->info_ssid = BAllocSize(bsize_add(bsize_fromsize(ssid_len), bsize_fromsize(1))))) { |
||
278 | ModuleLog(o->i, BLOG_ERROR, "BAllocSize failed"); |
||
279 | return 0; |
||
280 | } |
||
281 | memcpy(o->info_ssid, ssid, ssid_len); |
||
282 | o->info_ssid[ssid_len] = '\0'; |
||
283 | |||
284 | // set have info |
||
285 | o->have_info = 1; |
||
286 | |||
287 | return 1; |
||
288 | } |
||
289 | |||
290 | void free_info (struct instance *o) |
||
291 | { |
||
292 | ASSERT(o->have_info) |
||
293 | |||
294 | // free ssid |
||
295 | BFree(o->info_ssid); |
||
296 | |||
297 | // set not have info |
||
298 | o->have_info = 0; |
||
299 | } |
||
300 | |||
301 | void process_error (struct instance *o) |
||
302 | { |
||
303 | BInputProcess_Terminate(&o->process); |
||
304 | } |
||
305 | |||
306 | void process_handler_terminated (struct instance *o, int normally, uint8_t normally_exit_status) |
||
307 | { |
||
308 | ModuleLog(o->i, (o->dying ? BLOG_INFO : BLOG_ERROR), "process terminated"); |
||
309 | |||
310 | // die |
||
311 | instance_free(o, !o->dying); |
||
312 | return; |
||
313 | } |
||
314 | |||
315 | void process_handler_closed (struct instance *o, int is_error) |
||
316 | { |
||
317 | ASSERT(o->have_pipe) |
||
318 | |||
319 | if (is_error) { |
||
320 | ModuleLog(o->i, BLOG_ERROR, "pipe error"); |
||
321 | } else { |
||
322 | ModuleLog(o->i, BLOG_INFO, "pipe closed"); |
||
323 | } |
||
324 | |||
325 | // free buffer |
||
326 | LineBuffer_Free(&o->pipe_buffer); |
||
327 | |||
328 | // free input interface |
||
329 | PacketPassInterface_Free(&o->pipe_input); |
||
330 | |||
331 | // set have no pipe |
||
332 | o->have_pipe = 0; |
||
333 | } |
||
334 | |||
335 | void process_pipe_handler_send (struct instance *o, uint8_t *data, int data_len) |
||
336 | { |
||
337 | ASSERT(o->have_pipe) |
||
338 | ASSERT(data_len > 0) |
||
339 | |||
340 | // accept packet |
||
341 | PacketPassInterface_Done(&o->pipe_input); |
||
342 | |||
343 | if (o->dying) { |
||
344 | return; |
||
345 | } |
||
346 | |||
347 | // strip "interface: " from beginning of line. Older wpa_supplicant versions (<1.0) don't add this |
||
348 | // prefix, so don't fail if there isn't one. |
||
349 | size_t l1; |
||
350 | size_t l2; |
||
351 | 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, ": "))) { |
||
352 | data += l1 + l2; |
||
353 | data_len -= l1 + l2; |
||
354 | } |
||
355 | |||
356 | int have_bssid = 1; |
||
357 | uint8_t bssid[6]; |
||
358 | uint8_t *ssid; |
||
359 | int ssid_len; |
||
360 | if (parse_trying(data, data_len, bssid, &ssid, &ssid_len) || (have_bssid = 0, parse_trying_nobssid(data, data_len, &ssid, &ssid_len))) { |
||
361 | ModuleLog(o->i, BLOG_INFO, "trying event"); |
||
362 | |||
363 | if (o->up) { |
||
364 | ModuleLog(o->i, BLOG_ERROR, "trying unexpected!"); |
||
365 | process_error(o); |
||
366 | return; |
||
367 | } |
||
368 | |||
369 | if (o->have_info) { |
||
370 | free_info(o); |
||
371 | } |
||
372 | |||
373 | if (!init_info(o, have_bssid, bssid, ssid, ssid_len)) { |
||
374 | ModuleLog(o->i, BLOG_ERROR, "init_info failed"); |
||
375 | process_error(o); |
||
376 | return; |
||
377 | } |
||
378 | } |
||
379 | else if (data_begins_with((char *)data, data_len, EVENT_STRING_CONNECTED)) { |
||
380 | ModuleLog(o->i, BLOG_INFO, "connected event"); |
||
381 | |||
382 | if (o->up || !o->have_info) { |
||
383 | ModuleLog(o->i, BLOG_ERROR, "connected unexpected!"); |
||
384 | process_error(o); |
||
385 | return; |
||
386 | } |
||
387 | |||
388 | o->up = 1; |
||
389 | NCDModuleInst_Backend_Up(o->i); |
||
390 | } |
||
391 | else if (data_begins_with((char *)data, data_len, EVENT_STRING_DISCONNECTED)) { |
||
392 | ModuleLog(o->i, BLOG_INFO, "disconnected event"); |
||
393 | |||
394 | if (o->have_info) { |
||
395 | free_info(o); |
||
396 | } |
||
397 | |||
398 | if (o->up) { |
||
399 | o->up = 0; |
||
400 | NCDModuleInst_Backend_Down(o->i); |
||
401 | } |
||
402 | } |
||
403 | } |
||
404 | |||
405 | static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params) |
||
406 | { |
||
407 | struct instance *o = vo; |
||
408 | o->i = i; |
||
409 | |||
410 | // read arguments |
||
411 | NCDValRef ifname_arg; |
||
412 | NCDValRef conf_arg; |
||
413 | NCDValRef exec_arg; |
||
414 | NCDValRef args_arg; |
||
415 | if (!NCDVal_ListRead(params->args, 4, &ifname_arg, &conf_arg, &exec_arg, &args_arg)) { |
||
416 | ModuleLog(o->i, BLOG_ERROR, "wrong arity"); |
||
417 | goto fail0; |
||
418 | } |
||
419 | if (!NCDVal_IsStringNoNulls(ifname_arg) || !NCDVal_IsStringNoNulls(conf_arg) || |
||
420 | !NCDVal_IsStringNoNulls(exec_arg) || !NCDVal_IsList(args_arg)) { |
||
421 | ModuleLog(o->i, BLOG_ERROR, "wrong type"); |
||
422 | goto fail0; |
||
423 | } |
||
424 | |||
425 | o->ifname = NCDVal_StringMemRef(ifname_arg); |
||
426 | o->conf = NCDVal_StringMemRef(conf_arg); |
||
427 | o->exec = NCDVal_StringMemRef(exec_arg); |
||
428 | o->args = args_arg; |
||
429 | |||
430 | // set not dying |
||
431 | o->dying = 0; |
||
432 | |||
433 | // set not up |
||
434 | o->up = 0; |
||
435 | |||
436 | // build process cmdline |
||
437 | CmdLine c; |
||
438 | if (!build_cmdline(o, &c)) { |
||
439 | ModuleLog(o->i, BLOG_ERROR, "failed to build cmdline"); |
||
440 | goto fail0; |
||
441 | } |
||
442 | |||
443 | // init process |
||
444 | if (!BInputProcess_Init(&o->process, o->i->params->iparams->reactor, o->i->params->iparams->manager, o, |
||
445 | (BInputProcess_handler_terminated)process_handler_terminated, |
||
446 | (BInputProcess_handler_closed)process_handler_closed |
||
447 | )) { |
||
448 | ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Init failed"); |
||
449 | goto fail1; |
||
450 | } |
||
451 | |||
452 | // init input interface |
||
453 | PacketPassInterface_Init(&o->pipe_input, MAX_LINE_LEN, (PacketPassInterface_handler_send)process_pipe_handler_send, o, BReactor_PendingGroup(o->i->params->iparams->reactor)); |
||
454 | |||
455 | // init buffer |
||
456 | if (!LineBuffer_Init(&o->pipe_buffer, BInputProcess_GetInput(&o->process), &o->pipe_input, MAX_LINE_LEN, '\n')) { |
||
457 | ModuleLog(o->i, BLOG_ERROR, "LineBuffer_Init failed"); |
||
458 | goto fail2; |
||
459 | } |
||
460 | |||
461 | // set have pipe |
||
462 | o->have_pipe = 1; |
||
463 | |||
464 | // start process |
||
465 | if (!BInputProcess_Start(&o->process, ((char **)c.arr.v)[0], (char **)c.arr.v, NULL)) { |
||
466 | ModuleLog(o->i, BLOG_ERROR, "BInputProcess_Start failed"); |
||
467 | goto fail3; |
||
468 | } |
||
469 | |||
470 | // set not have info |
||
471 | o->have_info = 0; |
||
472 | |||
473 | CmdLine_Free(&c); |
||
474 | return; |
||
475 | |||
476 | fail3: |
||
477 | LineBuffer_Free(&o->pipe_buffer); |
||
478 | fail2: |
||
479 | PacketPassInterface_Free(&o->pipe_input); |
||
480 | BInputProcess_Free(&o->process); |
||
481 | fail1: |
||
482 | CmdLine_Free(&c); |
||
483 | fail0: |
||
484 | NCDModuleInst_Backend_DeadError(i); |
||
485 | } |
||
486 | |||
487 | void instance_free (struct instance *o, int is_error) |
||
488 | { |
||
489 | // free info |
||
490 | if (o->have_info) { |
||
491 | free_info(o); |
||
492 | } |
||
493 | |||
494 | if (o->have_pipe) { |
||
495 | // free buffer |
||
496 | LineBuffer_Free(&o->pipe_buffer); |
||
497 | |||
498 | // free input interface |
||
499 | PacketPassInterface_Free(&o->pipe_input); |
||
500 | } |
||
501 | |||
502 | // free process |
||
503 | BInputProcess_Free(&o->process); |
||
504 | |||
505 | if (is_error) { |
||
506 | NCDModuleInst_Backend_DeadError(o->i); |
||
507 | } else { |
||
508 | NCDModuleInst_Backend_Dead(o->i); |
||
509 | } |
||
510 | } |
||
511 | |||
512 | static void func_die (void *vo) |
||
513 | { |
||
514 | struct instance *o = vo; |
||
515 | ASSERT(!o->dying) |
||
516 | |||
517 | // request termination |
||
518 | BInputProcess_Terminate(&o->process); |
||
519 | |||
520 | // remember dying |
||
521 | o->dying = 1; |
||
522 | } |
||
523 | |||
524 | static int func_getvar (void *vo, const char *name, NCDValMem *mem, NCDValRef *out) |
||
525 | { |
||
526 | struct instance *o = vo; |
||
527 | ASSERT(o->up) |
||
528 | ASSERT(o->have_info) |
||
529 | |||
530 | if (!strcmp(name, "bssid")) { |
||
531 | char str[18]; |
||
532 | |||
533 | if (!o->info_have_bssid) { |
||
534 | sprintf(str, "none"); |
||
535 | } else { |
||
536 | uint8_t *id = o->info_bssid; |
||
537 | 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]); |
||
538 | } |
||
539 | |||
540 | *out = NCDVal_NewString(mem, str); |
||
541 | return 1; |
||
542 | } |
||
543 | |||
544 | if (!strcmp(name, "ssid")) { |
||
545 | *out = NCDVal_NewString(mem, o->info_ssid); |
||
546 | return 1; |
||
547 | } |
||
548 | |||
549 | return 0; |
||
550 | } |
||
551 | |||
552 | static struct NCDModule modules[] = { |
||
553 | { |
||
554 | .type = "net.backend.wpa_supplicant", |
||
555 | .func_new2 = func_new, |
||
556 | .func_die = func_die, |
||
557 | .func_getvar = func_getvar, |
||
558 | .alloc_size = sizeof(struct instance) |
||
559 | }, { |
||
560 | .type = NULL |
||
561 | } |
||
562 | }; |
||
563 | |||
564 | const struct NCDModuleGroup ncdmodule_net_backend_wpa_supplicant = { |
||
565 | .modules = modules |
||
566 | }; |