network.wifi.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. package main
  2. import (
  3. "runtime"
  4. "net/http"
  5. "encoding/json"
  6. "os/exec"
  7. "io/ioutil"
  8. "errors"
  9. "sort"
  10. "time"
  11. "log"
  12. "os"
  13. "strings"
  14. "path/filepath"
  15. )
  16. /*
  17. Network WiFi Module
  18. This module handle wifi connections and scanning on the devices that support wpa_supplicant like the Raspberry Pi
  19. Require service launch with Dbus (Work well on stock Raspberry Pi OS)
  20. */
  21. type WiFiInfo struct{
  22. Address string
  23. Channel int
  24. Frequency string
  25. Quality string
  26. SignalLevel string
  27. EncryptionKey bool
  28. ESSID string
  29. ConnectedBefore bool
  30. }
  31. func network_wifi_init(){
  32. //Only activate script on linux and if hardware management is enabled
  33. if runtime.GOOS == "linux" && *allow_hardware_management == true{
  34. //Register endpoints
  35. http.HandleFunc("/system/network/scanWifi", network_wifi_handleScan)
  36. http.HandleFunc("/system/network/connectWifi", network_wifi_handleConnect)
  37. http.HandleFunc("/system/network/removeWifi", network_wifi_handleWiFiRemove)
  38. http.HandleFunc("/system/network/wifiinfo", network_wifi_handleWiFiInfo)
  39. //Register WiFi Settings if system have WiFi interface
  40. wlanInterfaces, _ := network_wifi_getWirelessInterfaces()
  41. if len(wlanInterfaces) > 0 {
  42. //Contain at least 1 wireless interface Register System Settings
  43. registerSetting(settingModule{
  44. Name: "WiFi Info",
  45. Desc: "Current Connected WiFi Information",
  46. IconPath: "SystemAO/network/img/WiFi.png",
  47. Group: "Network",
  48. StartDir: "SystemAO/network/wifiinfo.html",
  49. })
  50. registerSetting(settingModule{
  51. Name: "WiFi Settings",
  52. Desc: "Setup WiFi Conenctions",
  53. IconPath: "SystemAO/network/img/WiFi.png",
  54. Group: "Network",
  55. StartDir: "SystemAO/network/wifi.html",
  56. RequireAdmin: true,
  57. })
  58. }
  59. }
  60. }
  61. func network_wifi_handleScan(w http.ResponseWriter, r *http.Request){
  62. //Require admin permission to scan and connect wifi
  63. isAdmin := system_permission_checkUserIsAdmin(w,r)
  64. if !isAdmin{
  65. sendErrorResponse(w, "Permission Denied")
  66. return
  67. }
  68. //Get a list of current on system wireless interface
  69. wirelessInterfaces, err := network_wifi_getWirelessInterfaces()
  70. if err != nil{
  71. sendErrorResponse(w, err.Error())
  72. return
  73. }
  74. if len(wirelessInterfaces) == 0{
  75. //No wireless interface
  76. sendErrorResponse(w, "Wireless Interface Not Found")
  77. return
  78. }
  79. //Get the first ethernet interface and use it to scan nearby wifi
  80. scannedWiFiInfo, err := network_wifi_scanNearbyWifi(wirelessInterfaces[0]);
  81. if err != nil{
  82. sendErrorResponse(w, err.Error())
  83. return
  84. }
  85. jsonString, _ := json.Marshal(scannedWiFiInfo)
  86. sendJSONResponse(w, string(jsonString))
  87. }
  88. //Scan all nearby WiFi
  89. func network_wifi_scanNearbyWifi(interfaceName string) ([]WiFiInfo, error){
  90. rcmd := `iwlist ` + interfaceName + ` scan`
  91. if sudo_mode {
  92. rcmd = "sudo " + rcmd
  93. }
  94. cmd := exec.Command("bash", "-c", rcmd)
  95. out, err := cmd.CombinedOutput()
  96. if err != nil {
  97. return []WiFiInfo{}, err
  98. }
  99. //parse the output of the WiFi Scan
  100. lines := strings.Split(strings.TrimSpace(string(out)), "\n")
  101. for i, thisline := range lines{
  102. lines[i] = strings.TrimSpace(thisline)
  103. }
  104. //Ignore first line if it contains "Scan completed"
  105. if strings.Contains(lines[0], "Scan completed"){
  106. lines = lines[1:]
  107. }
  108. var results = []WiFiInfo{}
  109. //Loop through each line and construct the WiFi Info slice
  110. processingWiFiNode := new(WiFiInfo)
  111. for _, line := range lines{
  112. if strings.Contains(line, "Address: "){
  113. //Push the previous results into results and create a new Node
  114. if processingWiFiNode.Address != ""{
  115. //Check if the ESSID already exists
  116. if (fileExists("./system/network/wifi/ap/" + processingWiFiNode.ESSID + ".config")){
  117. processingWiFiNode.ConnectedBefore = true
  118. }else{
  119. processingWiFiNode.ConnectedBefore = false
  120. }
  121. results = append(results, *processingWiFiNode)
  122. processingWiFiNode = new(WiFiInfo)
  123. }
  124. //Analysis this node
  125. datachunk := strings.Split(line, " ")
  126. if len(datachunk) > 0{
  127. processingWiFiNode.Address = datachunk[len(datachunk)-1]
  128. }
  129. }else if strings.Contains(line, "Channel") && strings.Contains(line, "Frequency") == false{
  130. datachunk := strings.Split(line, ":")
  131. if len(datachunk) > 0{
  132. channel, err := StringToInt(datachunk[len(datachunk)-1])
  133. if (err != nil){
  134. channel = -1;
  135. }
  136. processingWiFiNode.Channel = channel
  137. }
  138. }else if strings.Contains(line, "Frequency"){
  139. tmp := strings.Split(line, ":")
  140. if len(tmp) > 0{
  141. frequencyData := tmp[len(tmp) - 1]
  142. frequencyDataChunk := strings.Split(frequencyData, " ")
  143. if len(frequencyDataChunk) > 1{
  144. frequencyString := frequencyDataChunk[:2]
  145. processingWiFiNode.Frequency = strings.Join(frequencyString, " ")
  146. }
  147. }
  148. }else if strings.Contains(line, "Quality="){
  149. //Need to seperate quality data from signal level. Example source: Quality=70/70 Signal level=-40 dBm
  150. analysisItem := strings.Split(line, " ")
  151. if (len(analysisItem) == 2){
  152. //Get the quality of connections
  153. processingWiFiNode.Quality = analysisItem[0][8:]
  154. //Get the signal level of the connections
  155. processingWiFiNode.SignalLevel = analysisItem[1][13:]
  156. }
  157. }else if strings.Contains(line, "Encryption key"){
  158. ek := strings.Split(line, ":")
  159. if len(ek) > 0{
  160. status := ek[1]
  161. if status == "on"{
  162. processingWiFiNode.EncryptionKey = true
  163. }else{
  164. processingWiFiNode.EncryptionKey = false
  165. }
  166. }
  167. }else if strings.Contains(line, "ESSID"){
  168. iddata := strings.Split(line, ":")
  169. if len(iddata) > 0{
  170. ESSID := iddata[1]
  171. ESSID = strings.ReplaceAll(ESSID, "\"","")
  172. if ESSID == ""{
  173. ESSID = "Hidden Network"
  174. }
  175. processingWiFiNode.ESSID = ESSID
  176. }
  177. }
  178. }
  179. return results, nil
  180. }
  181. func network_wifi_getWirelessInterfaces() ([]string, error){
  182. //Get all the network interfaces
  183. rcmd := `iw dev | awk '$1=="Interface"{print $2}'`
  184. cmd := exec.Command("bash", "-c", rcmd)
  185. out, err := cmd.CombinedOutput()
  186. if err != nil {
  187. log.Println(string(out))
  188. return []string{}, errors.New(string(out))
  189. }
  190. interfaces := strings.Split(strings.TrimSpace(string(out)), "\n")
  191. sort.Strings(interfaces)
  192. return interfaces, nil
  193. }
  194. func network_wifi_handleConnect(w http.ResponseWriter, r *http.Request){
  195. //Get information from client and create a new network config file
  196. isAdmin := system_permission_checkUserIsAdmin(w,r)
  197. if !isAdmin{
  198. sendErrorResponse(w, "Permission denied")
  199. return
  200. }
  201. ssid, err := mv(r, "ESSID", true)
  202. if err != nil{
  203. sendErrorResponse(w, "ESSID not given")
  204. return
  205. }
  206. connType, _ := mv(r, "ConnType", true)
  207. password, _ := mv(r, "pwd", true)
  208. log.Println("WiFi Switch Request Received. Genering Network Configuration...")
  209. //Build the network config file
  210. //DO NOT TOUCH THE INDENTATION!! THEY MUST BE KEEP LIKE THIS
  211. writeToConfig := true
  212. networkConfigFile := ""
  213. if (connType == ""){
  214. //Home use network / WPA2
  215. if password == ""{
  216. //No need password
  217. networkConfigFile = `network={
  218. ssid="`+ ssid + `"
  219. key_mgmt=NONE
  220. priority={{priority}}
  221. }`
  222. }else{
  223. networkConfigFile = `network={
  224. ssid="` + ssid + `"
  225. psk="` + password + `"
  226. priority={{priority}}
  227. }`
  228. }
  229. }else if (connType == "WPA-EAP"){
  230. identity, err := mv(r, "identity", true)
  231. if err != nil{
  232. sendErrorResponse(w, "Identity not defined")
  233. return
  234. }
  235. networkConfigFile = `network={
  236. ssid="` + ssid + `"
  237. key_mgmt=WPA-EAP
  238. identity="` + identity + `"
  239. password="` + password + `"
  240. }`;
  241. }else if (connType == "switch"){
  242. //Special case, for handling WiFi Switching without retyping the password
  243. writeToConfig = false
  244. }else{
  245. sendErrorResponse(w, "Unsupported Connection Type")
  246. return
  247. }
  248. //Generate new wpa_supplicant_conf from file
  249. if !fileExists("./system/network/wifi/ap"){
  250. os.MkdirAll("./system/network/wifi/ap", 0755)
  251. }
  252. if writeToConfig == true{
  253. log.Println("WiFi Config Generated. Writing to file...")
  254. //Write config file to disk
  255. err = ioutil.WriteFile("./system/network/wifi/ap/" + ssid + ".config", []byte(networkConfigFile), 0755)
  256. if err != nil{
  257. sendErrorResponse(w, err.Error())
  258. return
  259. }
  260. }else{
  261. log.Println("Switching WiFi AP...")
  262. }
  263. //Start creating the new wpa_supplicant file
  264. //Get header
  265. configHeader, err := ioutil.ReadFile("./system/network/wifi/wpa_supplicant.conf_template.config")
  266. if err != nil{
  267. //Template header not found. Use default one from Raspberry Pi
  268. log.Println("Warning! wpa_supplicant template file not found. Using default template.")
  269. configHeader = []byte(`ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
  270. update_config=1
  271. {{networks}}
  272. `);
  273. }
  274. //Build network informations
  275. networksConfigs, err := filepath.Glob("./system/network/wifi/ap/*.config")
  276. if err != nil{
  277. sendErrorResponse(w, err.Error())
  278. return
  279. }
  280. //Read each of the network and append it into a string slice
  281. networks := []string{}
  282. for _, configFile := range networksConfigs{
  283. thisNetworkConfig, err := ioutil.ReadFile(configFile)
  284. if err != nil{
  285. log.Println("Failed to read Network Config File: " + configFile)
  286. continue;
  287. }
  288. if (strings.TrimSuffix(filepath.Base(configFile), filepath.Ext(configFile)) == ssid){
  289. //The new SSID. Set this to higher priority
  290. networks = append(networks, template_apply(string(thisNetworkConfig),map[string]interface{}{
  291. "priority": IntToString(1),
  292. }))
  293. }else{
  294. //Old SSID. Use default priority
  295. networks = append(networks, template_apply(string(thisNetworkConfig),map[string]interface{}{
  296. "priority": IntToString(0),
  297. }))
  298. }
  299. }
  300. //Subsitute the results into the template
  301. networksConfigString := strings.Join(networks, "\n")
  302. newconfig := template_apply(string(configHeader), map[string]interface{}{
  303. "networks": networksConfigString,
  304. })
  305. //Try to write the new config to wpa_supplicant
  306. err = ioutil.WriteFile(*wpa_supplicant_path, []byte(newconfig), 0777)
  307. if err != nil{
  308. log.Println("Failed to update wpa_supplicant config, are you sure you have access permission to that file?")
  309. sendErrorResponse(w, err.Error())
  310. }
  311. log.Println("WiFi Config Updated. Restarting Wireless Interfaces...")
  312. //Restart network services
  313. cmd := exec.Command("wpa_cli", "-i", *wan_interface_name, "reconfigure")
  314. out, err := cmd.CombinedOutput()
  315. if err != nil {
  316. sendErrorResponse(w,string(out))
  317. return
  318. }
  319. log.Println("Trying to connect new AP")
  320. //Wait until the WiFi is conencted
  321. rescanCount := 0
  322. connectedSSID, _, _:= network_wifi_getConnectedWiFi()
  323. //Wait for 30 seconds
  324. for rescanCount < 10 && connectedSSID == ""{
  325. connectedSSID, _, _ = network_wifi_getConnectedWiFi()
  326. log.Println(connectedSSID)
  327. rescanCount = rescanCount + 1
  328. log.Println("Waiting WiFi Connection (Retry " + IntToString(rescanCount) + "/10)")
  329. time.Sleep(3 * time.Second)
  330. }
  331. type conenctionResult struct{
  332. ConnectedSSID string
  333. Success bool
  334. }
  335. result := new(conenctionResult)
  336. if (rescanCount) >= 10{
  337. result.Success = false
  338. }else{
  339. result.ConnectedSSID = connectedSSID
  340. result.Success = true
  341. }
  342. jsonString, err := json.Marshal(result)
  343. if err != nil{
  344. sendErrorResponse(w, err.Error())
  345. return
  346. }
  347. sendJSONResponse(w, string(jsonString))
  348. log.Println("WiFi Connected")
  349. }
  350. //Get the current connected WiFi SSID and interface
  351. func network_wifi_getConnectedWiFi()(string, string, error){
  352. cmd := exec.Command("iwgetid")
  353. out, err := cmd.CombinedOutput()
  354. if err != nil {
  355. return "","",errors.New(string(out))
  356. }
  357. if len(string(out)) == 0{
  358. return "","",nil
  359. }
  360. //Try to parse the data
  361. trimmedData := string(out)
  362. for strings.Contains(trimmedData, " "){
  363. trimmedData = strings.ReplaceAll(trimmedData, " "," ")
  364. }
  365. dc := strings.Split(trimmedData, " ")
  366. wlanInterface := dc[0]
  367. ESSID := strings.Join(dc[1:]," ")[7:]
  368. ESSID = strings.TrimSpace(ESSID)
  369. ESSID = ESSID[:len(ESSID) - 1]
  370. if strings.TrimSpace(ESSID) == "\""{
  371. ESSID = ""
  372. }
  373. return ESSID, wlanInterface, nil
  374. }
  375. func network_wifi_handleWiFiRemove(w http.ResponseWriter, r *http.Request){
  376. //Require admin permission to scan and connect wifi
  377. isAdmin := system_permission_checkUserIsAdmin(w,r)
  378. if !isAdmin{
  379. sendErrorResponse(w, "Permission Denied")
  380. return
  381. }
  382. //Get ESSID from post request
  383. ESSID, err := mv(r, "ESSID", true)
  384. if err != nil{
  385. sendErrorResponse(w, "ESSID not given")
  386. return
  387. }
  388. //Check if the ESSID entry exists
  389. //Check the path for safty
  390. if !system_fs_checkFileInDirectory("./system/network/wifi/ap/" + ESSID + ".config", "./system/network/wifi/ap/"){
  391. sendErrorResponse(w, "Invalid ESSID")
  392. return
  393. }
  394. if fileExists("./system/network/wifi/ap/" + ESSID + ".config"){
  395. os.Remove("./system/network/wifi/ap/" + ESSID + ".config")
  396. }else{
  397. sendErrorResponse(w, "Record not found")
  398. return
  399. }
  400. sendOK(w)
  401. }
  402. func network_wifi_handleWiFiInfo(w http.ResponseWriter, r *http.Request){
  403. //Get and return the current conencted WiFi Information
  404. _, err := system_auth_getUserName(w, r)
  405. if err != nil {
  406. sendErrorResponse(w, "User not logged in")
  407. return
  408. }
  409. ESSID, interfaceName, err := network_wifi_getConnectedWiFi()
  410. if err != nil{
  411. sendErrorResponse(w, "Failed to retrieve WiFi Information");
  412. return
  413. }
  414. jsonString, _ := json.Marshal(map[string]string{
  415. "ESSID" : ESSID,
  416. "Interface": interfaceName,
  417. })
  418. sendJSONResponse(w, string(jsonString))
  419. }