hostapd: add support for authenticating with multiple PSKs via ubus helper
[openwrt/staging/nbd.git] / package / network / services / hostapd / files / hostapd.uc
1 let libubus = require("ubus");
2 import { open, readfile } from "fs";
3 import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
4
5 let ubus = libubus.connect(null, 60);
6
7 hostapd.data.config = {};
8 hostapd.data.pending_config = {};
9
10 hostapd.data.file_fields = {
11 vlan_file: true,
12 wpa_psk_file: true,
13 accept_mac_file: true,
14 deny_mac_file: true,
15 eap_user_file: true,
16 ca_cert: true,
17 server_cert: true,
18 server_cert2: true,
19 private_key: true,
20 private_key2: true,
21 dh_file: true,
22 eap_sim_db: true,
23 };
24
25 function iface_remove(cfg)
26 {
27 if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
28 return;
29
30 for (let bss in cfg.bss)
31 wdev_remove(bss.ifname);
32 }
33
34 function iface_gen_config(phy, config, start_disabled)
35 {
36 let str = `data:
37 ${join("\n", config.radio.data)}
38 channel=${config.radio.channel}
39 `;
40
41 for (let i = 0; i < length(config.bss); i++) {
42 let bss = config.bss[i];
43 let type = i > 0 ? "bss" : "interface";
44 let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
45
46 str += `
47 ${type}=${bss.ifname}
48 bssid=${bss.bssid}
49 ${join("\n", bss.data)}
50 nas_identifier=${nasid}
51 `;
52 if (start_disabled)
53 str += `
54 start_disabled=1
55 `;
56 }
57
58 return str;
59 }
60
61 function iface_freq_info(iface, config, params)
62 {
63 let freq = params.frequency;
64 if (!freq)
65 return null;
66
67 let sec_offset = params.sec_chan_offset;
68 if (sec_offset != -1 && sec_offset != 1)
69 sec_offset = 0;
70
71 let width = 0;
72 for (let line in config.radio.data) {
73 if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
74 sec_offset = null; // auto-detect
75 continue;
76 }
77
78 let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
79 if (!val)
80 continue;
81
82 val = int(val[2]);
83 if (val > width)
84 width = val;
85 }
86
87 if (freq < 4000)
88 width = 0;
89
90 return hostapd.freq_info(freq, sec_offset, width);
91 }
92
93 function iface_add(phy, config, phy_status)
94 {
95 let config_inline = iface_gen_config(phy, config, !!phy_status);
96
97 let bss = config.bss[0];
98 let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
99 if (ret < 0)
100 return false;
101
102 if (!phy_status)
103 return true;
104
105 let iface = hostapd.interfaces[phy];
106 if (!iface)
107 return false;
108
109 let freq_info = iface_freq_info(iface, config, phy_status);
110
111 return iface.start(freq_info) >= 0;
112 }
113
114 function iface_config_macaddr_list(config)
115 {
116 let macaddr_list = {};
117 for (let i = 0; i < length(config.bss); i++) {
118 let bss = config.bss[i];
119 if (!bss.default_macaddr)
120 macaddr_list[bss.bssid] = i;
121 }
122
123 return macaddr_list;
124 }
125
126 function iface_update_supplicant_macaddr(phy, config)
127 {
128 let macaddr_list = [];
129 for (let i = 0; i < length(config.bss); i++)
130 push(macaddr_list, config.bss[i].bssid);
131 ubus.defer("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
132 }
133
134 function __iface_pending_next(pending, state, ret, data)
135 {
136 let config = pending.config;
137 let phydev = pending.phydev;
138 let phy = pending.phy;
139 let bss = config.bss[0];
140
141 if (pending.defer)
142 pending.defer.abort();
143 delete pending.defer;
144 switch (state) {
145 case "init":
146 let macaddr_list = [];
147 for (let i = 0; i < length(config.bss); i++)
148 push(macaddr_list, config.bss[i].bssid);
149 pending.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
150 return "create_bss";
151 case "create_bss":
152 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
153 if (err) {
154 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
155 return null;
156 }
157
158 pending.call("wpa_supplicant", "phy_status", { phy: phy });
159 return "check_phy";
160 case "check_phy":
161 let phy_status = data;
162 if (phy_status && phy_status.state == "COMPLETED") {
163 if (iface_add(phy, config, phy_status))
164 return "done";
165
166 hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
167 }
168 pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
169 return "wpas_stopped";
170 case "wpas_stopped":
171 if (!iface_add(phy, config))
172 hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
173 pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
174 return null;
175 case "done":
176 default:
177 delete hostapd.data.pending_config[phy];
178 break;
179 }
180 }
181
182 function iface_pending_next(ret, data)
183 {
184 let pending = true;
185 let cfg = this;
186
187 while (pending) {
188 this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
189 if (!this.next_state) {
190 __iface_pending_next(cfg, "done");
191 return;
192 }
193 pending = !this.defer;
194 }
195 }
196
197 function iface_pending_abort()
198 {
199 this.next_state = "done";
200 this.next();
201 }
202
203 function iface_pending_ubus_call(obj, method, arg)
204 {
205 let ubus = hostapd.data.ubus;
206 let pending = this;
207 this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
208 }
209
210 const iface_pending_proto = {
211 next: iface_pending_next,
212 call: iface_pending_ubus_call,
213 abort: iface_pending_abort,
214 };
215
216 function iface_pending_init(phydev, config)
217 {
218 let phy = phydev.name;
219
220 let pending = proto({
221 next_state: "init",
222 phydev: phydev,
223 phy: phy,
224 config: config,
225 next: iface_pending_next,
226 }, iface_pending_proto);
227
228 hostapd.data.pending_config[phy] = pending;
229 pending.next();
230 }
231
232 function iface_restart(phydev, config, old_config)
233 {
234 let phy = phydev.name;
235 let pending = hostapd.data.pending_config[phy];
236
237 if (pending)
238 pending.abort();
239
240 hostapd.remove_iface(phy);
241 iface_remove(old_config);
242 iface_remove(config);
243
244 if (!config.bss || !config.bss[0]) {
245 hostapd.printf(`No bss for phy ${phy}`);
246 return;
247 }
248
249 phydev.macaddr_init(iface_config_macaddr_list(config));
250 for (let i = 0; i < length(config.bss); i++) {
251 let bss = config.bss[i];
252 if (bss.default_macaddr)
253 bss.bssid = phydev.macaddr_next();
254 }
255
256 iface_pending_init(phydev, config);
257 }
258
259 function array_to_obj(arr, key, start)
260 {
261 let obj = {};
262
263 start ??= 0;
264 for (let i = start; i < length(arr); i++) {
265 let cur = arr[i];
266 obj[cur[key]] = cur;
267 }
268
269 return obj;
270 }
271
272 function find_array_idx(arr, key, val)
273 {
274 for (let i = 0; i < length(arr); i++)
275 if (arr[i][key] == val)
276 return i;
277
278 return -1;
279 }
280
281 function bss_reload_psk(bss, config, old_config)
282 {
283 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
284 return;
285
286 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
287 if (!is_equal(old_config, config))
288 return;
289
290 let ret = bss.ctrl("RELOAD_WPA_PSK");
291 ret ??= "failed";
292
293 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
294 }
295
296 function remove_file_fields(config)
297 {
298 return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
299 }
300
301 function bss_remove_file_fields(config)
302 {
303 let new_cfg = {};
304
305 for (let key in config)
306 new_cfg[key] = config[key];
307 new_cfg.data = remove_file_fields(new_cfg.data);
308 new_cfg.hash = {};
309 for (let key in config.hash)
310 new_cfg.hash[key] = config.hash[key];
311 delete new_cfg.hash.wpa_psk_file;
312 delete new_cfg.hash.vlan_file;
313
314 return new_cfg;
315 }
316
317 function bss_config_hash(config)
318 {
319 return hostapd.sha1(remove_file_fields(config) + "");
320 }
321
322 function bss_find_existing(config, prev_config, prev_hash)
323 {
324 let hash = bss_config_hash(config.data);
325
326 for (let i = 0; i < length(prev_config.bss); i++) {
327 if (!prev_hash[i] || hash != prev_hash[i])
328 continue;
329
330 prev_hash[i] = null;
331 return i;
332 }
333
334 return -1;
335 }
336
337 function get_config_bss(config, idx)
338 {
339 if (!config.bss[idx]) {
340 hostapd.printf(`Invalid bss index ${idx}`);
341 return null;
342 }
343
344 let ifname = config.bss[idx].ifname;
345 if (!ifname)
346 hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
347
348 return hostapd.bss[ifname];
349 }
350
351 function iface_reload_config(phydev, config, old_config)
352 {
353 let phy = phydev.name;
354
355 if (!old_config || !is_equal(old_config.radio, config.radio))
356 return false;
357
358 if (is_equal(old_config.bss, config.bss))
359 return true;
360
361 if (hostapd.data.pending_config[phy])
362 return false;
363
364 if (!old_config.bss || !old_config.bss[0])
365 return false;
366
367 let iface = hostapd.interfaces[phy];
368 let iface_name = old_config.bss[0].ifname;
369 if (!iface) {
370 hostapd.printf(`Could not find previous interface ${iface_name}`);
371 return false;
372 }
373
374 let first_bss = hostapd.bss[iface_name];
375 if (!first_bss) {
376 hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
377 return false;
378 }
379
380 let macaddr_list = iface_config_macaddr_list(config);
381 let bss_list = [];
382 let bss_list_cfg = [];
383 let prev_bss_hash = [];
384
385 for (let bss in old_config.bss) {
386 let hash = bss_config_hash(bss.data);
387 push(prev_bss_hash, bss_config_hash(bss.data));
388 }
389
390 // Step 1: find (possibly renamed) interfaces with the same config
391 // and store them in the new order (with gaps)
392 for (let i = 0; i < length(config.bss); i++) {
393 let prev;
394
395 // For fullmac devices, the first interface needs to be preserved,
396 // since it's treated as the master
397 if (!i && phy_is_fullmac(phy)) {
398 prev = 0;
399 prev_bss_hash[0] = null;
400 } else {
401 prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
402 }
403 if (prev < 0)
404 continue;
405
406 let cur_config = config.bss[i];
407 let prev_config = old_config.bss[prev];
408
409 let prev_bss = get_config_bss(old_config, prev);
410 if (!prev_bss)
411 return false;
412
413 // try to preserve MAC address of this BSS by reassigning another
414 // BSS if necessary
415 if (cur_config.default_macaddr &&
416 !macaddr_list[prev_config.bssid]) {
417 macaddr_list[prev_config.bssid] = i;
418 cur_config.bssid = prev_config.bssid;
419 }
420
421 bss_list[i] = prev_bss;
422 bss_list_cfg[i] = old_config.bss[prev];
423 }
424
425 if (config.mbssid && !bss_list_cfg[0]) {
426 hostapd.printf("First BSS changed with MBSSID enabled");
427 return false;
428 }
429
430 // Step 2: if none were found, rename and preserve the first one
431 if (length(bss_list) == 0) {
432 // can't change the bssid of the first bss
433 if (config.bss[0].bssid != old_config.bss[0].bssid) {
434 if (!config.bss[0].default_macaddr) {
435 hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
436 return false;
437 }
438
439 config.bss[0].bssid = old_config.bss[0].bssid;
440 }
441
442 let prev_bss = get_config_bss(old_config, 0);
443 if (!prev_bss)
444 return false;
445
446 macaddr_list[config.bss[0].bssid] = 0;
447 bss_list[0] = prev_bss;
448 bss_list_cfg[0] = old_config.bss[0];
449 prev_bss_hash[0] = null;
450 }
451
452 // Step 3: delete all unused old interfaces
453 for (let i = 0; i < length(prev_bss_hash); i++) {
454 if (!prev_bss_hash[i])
455 continue;
456
457 let prev_bss = get_config_bss(old_config, i);
458 if (!prev_bss)
459 return false;
460
461 let ifname = old_config.bss[i].ifname;
462 hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
463 prev_bss.delete();
464 wdev_remove(ifname);
465 }
466
467 // Step 4: rename preserved interfaces, use temporary name on duplicates
468 let rename_list = [];
469 for (let i = 0; i < length(bss_list); i++) {
470 if (!bss_list[i])
471 continue;
472
473 let old_ifname = bss_list_cfg[i].ifname;
474 let new_ifname = config.bss[i].ifname;
475 if (old_ifname == new_ifname)
476 continue;
477
478 if (hostapd.bss[new_ifname]) {
479 new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
480 push(rename_list, i);
481 }
482
483 hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
484 if (!bss_list[i].rename(new_ifname)) {
485 hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
486 return false;
487 }
488
489 bss_list_cfg[i].ifname = new_ifname;
490 }
491
492 // Step 5: rename interfaces with temporary names
493 for (let i in rename_list) {
494 let new_ifname = config.bss[i].ifname;
495 if (!bss_list[i].rename(new_ifname)) {
496 hostapd.printf(`Failed to rename bss to ${new_ifname}`);
497 return false;
498 }
499 bss_list_cfg[i].ifname = new_ifname;
500 }
501
502 // Step 6: assign BSSID for newly created interfaces
503 let macaddr_data = {
504 num_global: config.num_global_macaddr ?? 1,
505 mbssid: config.mbssid ?? 0,
506 };
507 macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
508 for (let i = 0; i < length(config.bss); i++) {
509 if (bss_list[i])
510 continue;
511 let bsscfg = config.bss[i];
512
513 let mac_idx = macaddr_list[bsscfg.bssid];
514 if (mac_idx < 0)
515 macaddr_list[bsscfg.bssid] = i;
516 if (mac_idx == i)
517 continue;
518
519 // statically assigned bssid of the new interface is in conflict
520 // with the bssid of a reused interface. reassign the reused interface
521 if (!bsscfg.default_macaddr) {
522 // can't update bssid of the first BSS, need to restart
523 if (!mac_idx < 0)
524 return false;
525
526 bsscfg = config.bss[mac_idx];
527 }
528
529 let addr = phydev.macaddr_next(i);
530 if (!addr) {
531 hostapd.printf(`Failed to generate mac address for phy ${phy}`);
532 return false;
533 }
534 bsscfg.bssid = addr;
535 }
536
537 let config_inline = iface_gen_config(phy, config);
538
539 // Step 7: fill in the gaps with new interfaces
540 for (let i = 0; i < length(config.bss); i++) {
541 let ifname = config.bss[i].ifname;
542 let bss = bss_list[i];
543
544 if (bss)
545 continue;
546
547 hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
548 bss_list[i] = iface.add_bss(config_inline, i);
549 if (!bss_list[i]) {
550 hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
551 return false;
552 }
553 }
554
555 // Step 8: update interface bss order
556 if (!iface.set_bss_order(bss_list)) {
557 hostapd.printf(`Failed to update BSS order on phy '${phy}'`);
558 return false;
559 }
560
561 // Step 9: update config
562 for (let i = 0; i < length(config.bss); i++) {
563 if (!bss_list_cfg[i])
564 continue;
565
566 let ifname = config.bss[i].ifname;
567 let bss = bss_list[i];
568
569 if (is_equal(config.bss[i], bss_list_cfg[i]))
570 continue;
571
572 if (is_equal(bss_remove_file_fields(config.bss[i]),
573 bss_remove_file_fields(bss_list_cfg[i]))) {
574 hostapd.printf(`Update config data files for bss ${ifname}`);
575 if (bss.set_config(config_inline, i, true) < 0) {
576 hostapd.printf(`Could not update config data files for bss ${ifname}`);
577 return false;
578 } else {
579 bss.ctrl("RELOAD_WPA_PSK");
580 continue;
581 }
582 }
583
584 bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
585 if (is_equal(config.bss[i], bss_list_cfg[i]))
586 continue;
587
588 hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
589 if (bss.set_config(config_inline, i) < 0) {
590 hostapd.printf(`Failed to set config for bss ${ifname}`);
591 return false;
592 }
593 }
594
595 return true;
596 }
597
598 function iface_set_config(phy, config)
599 {
600 let old_config = hostapd.data.config[phy];
601
602 hostapd.data.config[phy] = config;
603
604 if (!config) {
605 hostapd.remove_iface(phy);
606 return iface_remove(old_config);
607 }
608
609 let phydev = phy_open(phy);
610 if (!phydev) {
611 hostapd.printf(`Failed to open phy ${phy}`);
612 return false;
613 }
614
615 try {
616 let ret = iface_reload_config(phydev, config, old_config);
617 if (ret) {
618 iface_update_supplicant_macaddr(phy, config);
619 hostapd.printf(`Reloaded settings for phy ${phy}`);
620 return 0;
621 }
622 } catch (e) {
623 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
624 }
625
626 hostapd.printf(`Restart interface for phy ${phy}`);
627 let ret = iface_restart(phydev, config, old_config);
628
629 return ret;
630 }
631
632 function config_add_bss(config, name)
633 {
634 let bss = {
635 ifname: name,
636 data: [],
637 hash: {}
638 };
639
640 push(config.bss, bss);
641
642 return bss;
643 }
644
645 function iface_load_config(filename)
646 {
647 let f = open(filename, "r");
648 if (!f)
649 return null;
650
651 let config = {
652 radio: {
653 data: []
654 },
655 bss: [],
656 orig_file: filename,
657 };
658
659 let bss;
660 let line;
661 while ((line = rtrim(f.read("line"), "\n")) != null) {
662 let val = split(line, "=", 2);
663 if (!val[0])
664 continue;
665
666 if (val[0] == "interface") {
667 bss = config_add_bss(config, val[1]);
668 break;
669 }
670
671 if (val[0] == "channel") {
672 config.radio.channel = val[1];
673 continue;
674 }
675
676 if (val[0] == "#num_global_macaddr" ||
677 val[0] == "mbssid")
678 config[val[0]] = int(val[1]);
679
680 push(config.radio.data, line);
681 }
682
683 while ((line = rtrim(f.read("line"), "\n")) != null) {
684 if (line == "#default_macaddr")
685 bss.default_macaddr = true;
686
687 let val = split(line, "=", 2);
688 if (!val[0])
689 continue;
690
691 if (val[0] == "bssid") {
692 bss.bssid = lc(val[1]);
693 continue;
694 }
695
696 if (val[0] == "nas_identifier")
697 bss.nasid = val[1];
698
699 if (val[0] == "bss") {
700 bss = config_add_bss(config, val[1]);
701 continue;
702 }
703
704 if (hostapd.data.file_fields[val[0]])
705 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
706
707 push(bss.data, line);
708 }
709 f.close();
710
711 return config;
712 }
713
714 function ex_wrap(func) {
715 return (req) => {
716 try {
717 let ret = func(req);
718 return ret;
719 } catch(e) {
720 hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
721 }
722 return libubus.STATUS_UNKNOWN_ERROR;
723 };
724 }
725
726 let main_obj = {
727 reload: {
728 args: {
729 phy: "",
730 },
731 call: ex_wrap(function(req) {
732 let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
733 for (let phy_name in phy_list) {
734 let phy = hostapd.data.config[phy_name];
735 let config = iface_load_config(phy.orig_file);
736 iface_set_config(phy_name, config);
737 }
738
739 return 0;
740 })
741 },
742 apsta_state: {
743 args: {
744 phy: "",
745 up: true,
746 frequency: 0,
747 sec_chan_offset: 0,
748 csa: true,
749 csa_count: 0,
750 },
751 call: ex_wrap(function(req) {
752 if (req.args.up == null || !req.args.phy)
753 return libubus.STATUS_INVALID_ARGUMENT;
754
755 let phy = req.args.phy;
756 let config = hostapd.data.config[phy];
757 if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
758 return 0;
759
760 let iface = hostapd.interfaces[phy];
761 if (!iface)
762 return 0;
763
764 if (!req.args.up) {
765 iface.stop();
766 return 0;
767 }
768
769 if (!req.args.frequency)
770 return libubus.STATUS_INVALID_ARGUMENT;
771
772 let freq_info = iface_freq_info(iface, config, req.args);
773 if (!freq_info)
774 return libubus.STATUS_UNKNOWN_ERROR;
775
776 let ret;
777 if (req.args.csa) {
778 freq_info.csa_count = req.args.csa_count ?? 10;
779 ret = iface.switch_channel(freq_info);
780 } else {
781 ret = iface.start(freq_info);
782 }
783 if (!ret)
784 return libubus.STATUS_UNKNOWN_ERROR;
785
786 return 0;
787 })
788 },
789 config_get_macaddr_list: {
790 args: {
791 phy: ""
792 },
793 call: ex_wrap(function(req) {
794 let phy = req.args.phy;
795 if (!phy)
796 return libubus.STATUS_INVALID_ARGUMENT;
797
798 let ret = {
799 macaddr: [],
800 };
801
802 let config = hostapd.data.config[phy];
803 if (!config)
804 return ret;
805
806 ret.macaddr = map(config.bss, (bss) => bss.bssid);
807 return ret;
808 })
809 },
810 config_set: {
811 args: {
812 phy: "",
813 config: "",
814 prev_config: "",
815 },
816 call: ex_wrap(function(req) {
817 let phy = req.args.phy;
818 let file = req.args.config;
819 let prev_file = req.args.prev_config;
820
821 if (!phy)
822 return libubus.STATUS_INVALID_ARGUMENT;
823
824 if (prev_file && !hostapd.data.config[phy]) {
825 let config = iface_load_config(prev_file);
826 if (config)
827 config.radio.data = [];
828 hostapd.data.config[phy] = config;
829 }
830
831 let config = iface_load_config(file);
832
833 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
834 iface_set_config(phy, config);
835
836 return {
837 pid: hostapd.getpid()
838 };
839 })
840 },
841 config_add: {
842 args: {
843 iface: "",
844 config: "",
845 },
846 call: ex_wrap(function(req) {
847 if (!req.args.iface || !req.args.config)
848 return libubus.STATUS_INVALID_ARGUMENT;
849
850 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
851 return libubus.STATUS_INVALID_ARGUMENT;
852
853 return {
854 pid: hostapd.getpid()
855 };
856 })
857 },
858 config_remove: {
859 args: {
860 iface: ""
861 },
862 call: ex_wrap(function(req) {
863 if (!req.args.iface)
864 return libubus.STATUS_INVALID_ARGUMENT;
865
866 hostapd.remove_iface(req.args.iface);
867 return 0;
868 })
869 },
870 };
871
872 hostapd.data.ubus = ubus;
873 hostapd.data.obj = ubus.publish("hostapd", main_obj);
874 hostapd.udebug_set("hostapd", hostapd.data.ubus);
875
876 function bss_event(type, name, data) {
877 let ubus = hostapd.data.ubus;
878
879 data ??= {};
880 data.name = name;
881 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
882 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
883 }
884
885 return {
886 shutdown: function() {
887 for (let phy in hostapd.data.config)
888 iface_set_config(phy, null);
889 hostapd.udebug_set(null);
890 hostapd.ubus.disconnect();
891 },
892 afc_request: function(iface, data) {
893 let ret = ubus.call("afc", "request", { data });
894 if (type(ret) != "object")
895 return;
896 return ret.data;
897 },
898 bss_add: function(name, obj) {
899 bss_event("add", name);
900 },
901 bss_reload: function(name, obj, reconf) {
902 bss_event("reload", name, { reconf: reconf != 0 });
903 },
904 bss_remove: function(name, obj) {
905 bss_event("remove", name);
906 }
907 };