config.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. package main
  2. import (
  3. "archive/zip"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strconv"
  11. "strings"
  12. "time"
  13. "imuslab.com/zoraxy/mod/dynamicproxy"
  14. "imuslab.com/zoraxy/mod/utils"
  15. )
  16. /*
  17. Reverse Proxy Configs
  18. The following section handle
  19. the reverse proxy configs
  20. */
  21. type Record struct {
  22. ProxyType string
  23. Rootname string
  24. ProxyTarget string
  25. UseTLS bool
  26. BypassGlobalTLS bool
  27. SkipTlsValidation bool
  28. RequireBasicAuth bool
  29. BasicAuthCredentials []*dynamicproxy.BasicAuthCredentials
  30. BasicAuthExceptionRules []*dynamicproxy.BasicAuthExceptionRule
  31. }
  32. // Save a reverse proxy config record to file
  33. func SaveReverseProxyConfigToFile(proxyConfigRecord *Record) error {
  34. //TODO: Make this accept new def types
  35. os.MkdirAll("./conf/proxy/", 0775)
  36. filename := getFilenameFromRootName(proxyConfigRecord.Rootname)
  37. //Generate record
  38. thisRecord := proxyConfigRecord
  39. //Write to file
  40. js, _ := json.MarshalIndent(thisRecord, "", " ")
  41. return os.WriteFile(filepath.Join("./conf/proxy/", filename), js, 0775)
  42. }
  43. // Save a running reverse proxy endpoint to file (with automatic endpoint to record conversion)
  44. func SaveReverseProxyEndpointToFile(proxyEndpoint *dynamicproxy.ProxyEndpoint) error {
  45. recordToSave, err := ConvertProxyEndpointToRecord(proxyEndpoint)
  46. if err != nil {
  47. return err
  48. }
  49. return SaveReverseProxyConfigToFile(recordToSave)
  50. }
  51. func RemoveReverseProxyConfigFile(rootname string) error {
  52. filename := getFilenameFromRootName(rootname)
  53. removePendingFile := strings.ReplaceAll(filepath.Join("./conf/proxy/", filename), "\\", "/")
  54. SystemWideLogger.Println("Config Removed: ", removePendingFile)
  55. if utils.FileExists(removePendingFile) {
  56. err := os.Remove(removePendingFile)
  57. if err != nil {
  58. SystemWideLogger.PrintAndLog("Proxy", "Unabel to remove config file", err)
  59. return err
  60. }
  61. }
  62. //File already gone
  63. return nil
  64. }
  65. // Return ptype, rootname and proxyTarget, error if any
  66. func LoadReverseProxyConfig(filename string) (*Record, error) {
  67. thisRecord := Record{
  68. ProxyType: "",
  69. Rootname: "",
  70. ProxyTarget: "",
  71. UseTLS: false,
  72. BypassGlobalTLS: false,
  73. SkipTlsValidation: false,
  74. RequireBasicAuth: false,
  75. BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
  76. BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
  77. }
  78. configContent, err := os.ReadFile(filename)
  79. if err != nil {
  80. return &thisRecord, err
  81. }
  82. //Unmarshal the content into config
  83. err = json.Unmarshal(configContent, &thisRecord)
  84. if err != nil {
  85. return &thisRecord, err
  86. }
  87. //Return it
  88. return &thisRecord, nil
  89. }
  90. // Convert a running proxy endpoint object into a save-able record struct
  91. func ConvertProxyEndpointToRecord(targetProxyEndpoint *dynamicproxy.ProxyEndpoint) (*Record, error) {
  92. thisProxyConfigRecord := Record{
  93. ProxyType: targetProxyEndpoint.GetProxyTypeString(),
  94. Rootname: targetProxyEndpoint.RootOrMatchingDomain,
  95. ProxyTarget: targetProxyEndpoint.Domain,
  96. UseTLS: targetProxyEndpoint.RequireTLS,
  97. BypassGlobalTLS: targetProxyEndpoint.BypassGlobalTLS,
  98. SkipTlsValidation: targetProxyEndpoint.SkipCertValidations,
  99. RequireBasicAuth: targetProxyEndpoint.RequireBasicAuth,
  100. BasicAuthCredentials: targetProxyEndpoint.BasicAuthCredentials,
  101. BasicAuthExceptionRules: targetProxyEndpoint.BasicAuthExceptionRules,
  102. }
  103. return &thisProxyConfigRecord, nil
  104. }
  105. func getFilenameFromRootName(rootname string) string {
  106. //Generate a filename for this rootname
  107. filename := strings.ReplaceAll(rootname, ".", "_")
  108. filename = strings.ReplaceAll(filename, "/", "-")
  109. filename = filename + ".config"
  110. return filename
  111. }
  112. /*
  113. Importer and Exporter of Zoraxy proxy config
  114. */
  115. func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
  116. includeSysDBRaw, err := utils.GetPara(r, "includeDB")
  117. includeSysDB := false
  118. if includeSysDBRaw == "true" {
  119. //Include the system database in backup snapshot
  120. //Temporary set it to read only
  121. sysdb.ReadOnly = true
  122. includeSysDB = true
  123. }
  124. // Specify the folder path to be zipped
  125. folderPath := "./conf/"
  126. // Set the Content-Type header to indicate it's a zip file
  127. w.Header().Set("Content-Type", "application/zip")
  128. // Set the Content-Disposition header to specify the file name
  129. w.Header().Set("Content-Disposition", "attachment; filename=\"config.zip\"")
  130. // Create a zip writer
  131. zipWriter := zip.NewWriter(w)
  132. defer zipWriter.Close()
  133. // Walk through the folder and add files to the zip
  134. err = filepath.Walk(folderPath, func(filePath string, fileInfo os.FileInfo, err error) error {
  135. if err != nil {
  136. return err
  137. }
  138. if folderPath == filePath {
  139. //Skip root folder
  140. return nil
  141. }
  142. // Create a new file in the zip
  143. if !utils.IsDir(filePath) {
  144. zipFile, err := zipWriter.Create(filePath)
  145. if err != nil {
  146. return err
  147. }
  148. // Open the file on disk
  149. file, err := os.Open(filePath)
  150. if err != nil {
  151. return err
  152. }
  153. defer file.Close()
  154. // Copy the file contents to the zip file
  155. _, err = io.Copy(zipFile, file)
  156. if err != nil {
  157. return err
  158. }
  159. }
  160. return nil
  161. })
  162. if includeSysDB {
  163. //Also zip in the sysdb
  164. zipFile, err := zipWriter.Create("sys.db")
  165. if err != nil {
  166. SystemWideLogger.PrintAndLog("Backup", "Unable to zip sysdb", err)
  167. return
  168. }
  169. // Open the file on disk
  170. file, err := os.Open("sys.db")
  171. if err != nil {
  172. SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
  173. return
  174. }
  175. defer file.Close()
  176. // Copy the file contents to the zip file
  177. _, err = io.Copy(zipFile, file)
  178. if err != nil {
  179. SystemWideLogger.Println(err)
  180. return
  181. }
  182. //Restore sysdb state
  183. sysdb.ReadOnly = false
  184. }
  185. if err != nil {
  186. // Handle the error and send an HTTP response with the error message
  187. http.Error(w, fmt.Sprintf("Failed to zip folder: %v", err), http.StatusInternalServerError)
  188. return
  189. }
  190. }
  191. func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
  192. // Check if the request is a POST with a file upload
  193. if r.Method != http.MethodPost {
  194. http.Error(w, "Invalid request method", http.StatusBadRequest)
  195. return
  196. }
  197. // Max file size limit (10 MB in this example)
  198. r.ParseMultipartForm(10 << 20)
  199. // Get the uploaded file
  200. file, handler, err := r.FormFile("file")
  201. if err != nil {
  202. http.Error(w, "Failed to retrieve uploaded file", http.StatusInternalServerError)
  203. return
  204. }
  205. defer file.Close()
  206. if filepath.Ext(handler.Filename) != ".zip" {
  207. http.Error(w, "Upload file is not a zip file", http.StatusInternalServerError)
  208. return
  209. }
  210. // Create the target directory to unzip the files
  211. targetDir := "./conf"
  212. if utils.FileExists(targetDir) {
  213. //Backup the old config to old
  214. os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix())))
  215. }
  216. err = os.MkdirAll(targetDir, os.ModePerm)
  217. if err != nil {
  218. http.Error(w, fmt.Sprintf("Failed to create target directory: %v", err), http.StatusInternalServerError)
  219. return
  220. }
  221. // Open the zip file
  222. zipReader, err := zip.NewReader(file, handler.Size)
  223. if err != nil {
  224. http.Error(w, fmt.Sprintf("Failed to open zip file: %v", err), http.StatusInternalServerError)
  225. return
  226. }
  227. restoreDatabase := false
  228. // Extract each file from the zip archive
  229. for _, zipFile := range zipReader.File {
  230. // Open the file in the zip archive
  231. rc, err := zipFile.Open()
  232. if err != nil {
  233. http.Error(w, fmt.Sprintf("Failed to open file in zip: %v", err), http.StatusInternalServerError)
  234. return
  235. }
  236. defer rc.Close()
  237. // Create the corresponding file on disk
  238. zipFile.Name = strings.ReplaceAll(zipFile.Name, "../", "")
  239. fmt.Println("Restoring: " + strings.ReplaceAll(zipFile.Name, "\\", "/"))
  240. if zipFile.Name == "sys.db" {
  241. //Sysdb replacement. Close the database and restore
  242. sysdb.Close()
  243. restoreDatabase = true
  244. } else if !strings.HasPrefix(strings.ReplaceAll(zipFile.Name, "\\", "/"), "conf/") {
  245. //Malformed zip file.
  246. http.Error(w, fmt.Sprintf("Invalid zip file structure or version too old"), http.StatusInternalServerError)
  247. return
  248. }
  249. //Check if parent dir exists
  250. if !utils.FileExists(filepath.Dir(zipFile.Name)) {
  251. os.MkdirAll(filepath.Dir(zipFile.Name), 0775)
  252. }
  253. //Create the file
  254. newFile, err := os.Create(zipFile.Name)
  255. if err != nil {
  256. http.Error(w, fmt.Sprintf("Failed to create file: %v", err), http.StatusInternalServerError)
  257. return
  258. }
  259. defer newFile.Close()
  260. // Copy the file contents from the zip to the new file
  261. _, err = io.Copy(newFile, rc)
  262. if err != nil {
  263. http.Error(w, fmt.Sprintf("Failed to extract file from zip: %v", err), http.StatusInternalServerError)
  264. return
  265. }
  266. }
  267. // Send a success response
  268. w.WriteHeader(http.StatusOK)
  269. SystemWideLogger.Println("Configuration restored")
  270. fmt.Fprintln(w, "Configuration restored")
  271. if restoreDatabase {
  272. go func() {
  273. SystemWideLogger.Println("Database altered. Restarting in 3 seconds...")
  274. time.Sleep(3 * time.Second)
  275. os.Exit(0)
  276. }()
  277. }
  278. }