github.com/piotrnar/gocoin@v0.0.0-20240512203912-faa0448c5e96/client/usif/webui/webui.go (about) 1 package webui 2 3 import ( 4 "crypto/rand" 5 "crypto/tls" 6 "crypto/x509" 7 "encoding/hex" 8 "fmt" 9 "io/ioutil" 10 "log" 11 "net/http" 12 "path/filepath" 13 "strings" 14 "time" 15 16 "github.com/piotrnar/gocoin" 17 "github.com/piotrnar/gocoin/client/common" 18 "github.com/piotrnar/gocoin/client/usif" 19 ) 20 21 var start_time time.Time 22 23 func ipchecker(r *http.Request) bool { 24 if common.NetworkClosed.Get() || usif.Exit_now.Get() { 25 return false 26 } 27 28 if r.TLS != nil { 29 r.ParseForm() 30 return true 31 } 32 33 var a, b, c, d uint32 34 n, _ := fmt.Sscanf(r.RemoteAddr, "%d.%d.%d.%d", &a, &b, &c, &d) 35 if n != 4 { 36 return false 37 } 38 addr := (a << 24) | (b << 16) | (c << 8) | d 39 common.LockCfg() 40 for i := range common.WebUIAllowed { 41 if (addr & common.WebUIAllowed[i].Mask) == common.WebUIAllowed[i].Addr { 42 common.UnlockCfg() 43 r.ParseForm() 44 return true 45 } 46 } 47 common.UnlockCfg() 48 println("ipchecker:", r.RemoteAddr, "is blocked") 49 return false 50 } 51 52 func load_template(fn string) string { 53 dat, er := ioutil.ReadFile("www/templates/" + fn) 54 if er != nil { 55 return er.Error() + "\n" 56 } 57 return string(dat) 58 } 59 60 func templ_add(tmpl string, id string, val string) string { 61 return strings.Replace(tmpl, id, val+id, 1) 62 } 63 64 func p_webui(w http.ResponseWriter, r *http.Request) { 65 if !ipchecker(r) { 66 return 67 } 68 69 pth := strings.SplitN(r.URL.Path[1:], "/", 3) 70 if len(pth) == 2 { 71 dat, _ := ioutil.ReadFile("www/resources/" + pth[1]) 72 if len(dat) > 0 { 73 switch filepath.Ext(r.URL.Path) { 74 case ".js": 75 w.Header()["Content-Type"] = []string{"text/javascript"} 76 case ".css": 77 w.Header()["Content-Type"] = []string{"text/css"} 78 } 79 w.Write(dat) 80 } else { 81 http.NotFound(w, r) 82 } 83 } 84 } 85 86 func sid(r *http.Request) string { 87 c, _ := r.Cookie("sid") 88 if c != nil { 89 return c.Value 90 } 91 return "" 92 } 93 94 func checksid(r *http.Request) bool { 95 if len(r.Form["sid"]) == 0 { 96 return false 97 } 98 if len(r.Form["sid"][0]) < 16 { 99 return false 100 } 101 return r.Form["sid"][0] == sid(r) 102 } 103 104 func new_session_id(w http.ResponseWriter) (sessid string) { 105 var sid [16]byte 106 rand.Read(sid[:]) 107 sessid = hex.EncodeToString(sid[:]) 108 http.SetCookie(w, &http.Cookie{Name: "sid", Value: sessid}) 109 return 110 } 111 112 func write_html_head(w http.ResponseWriter, r *http.Request) { 113 start_time = time.Now() 114 115 sessid := sid(r) 116 if sessid == "" { 117 sessid = new_session_id(w) 118 } 119 120 s := load_template("page_head.html") 121 s = strings.Replace(s, "{PAGE_TITLE}", common.CFG.WebUI.Title, 1) 122 s = strings.Replace(s, "/*_SESSION_ID_*/", "var sid = '"+sessid+"'", 1) 123 s = strings.Replace(s, "/*_AVERAGE_FEE_SPB_*/", fmt.Sprint("var avg_fee_spb = ", usif.GetAverageFee()), 1) 124 s = strings.Replace(s, "/*_SERVER_MODE_*/", fmt.Sprint("var server_mode = ", common.CFG.WebUI.ServerMode), 1) 125 s = strings.Replace(s, "/*_TIME_NOW_*/", fmt.Sprint("= ", time.Now().Unix()), 1) 126 s = strings.Replace(s, "/*_WALLET_ON_*/", fmt.Sprint("var wallet_on = ", common.GetBool(&common.WalletON)), 1) 127 s = strings.Replace(s, "/*_CHAIN_IN_SYNC_*/", fmt.Sprint("var chain_in_sync = ", common.GetBool(&common.BlockChainSynchronized)), 1) 128 129 if r.URL.Path != "/" { 130 s = strings.Replace(s, "{HELPURL}", "help#"+r.URL.Path[1:], 1) 131 } else { 132 s = strings.Replace(s, "{HELPURL}", "help", 1) 133 } 134 s = strings.Replace(s, "{VERSION}", gocoin.Version, 1) 135 if common.Testnet { 136 s = strings.Replace(s, "{TESTNET}", " Testnet ", 1) 137 } else { 138 s = strings.Replace(s, "{TESTNET}", "", 1) 139 } 140 141 w.Write([]byte(s)) 142 } 143 144 func write_html_tail(w http.ResponseWriter) { 145 s := load_template("page_tail.html") 146 s = strings.Replace(s, "<!--LOAD_TIME-->", time.Now().Sub(start_time).String(), 1) 147 w.Write([]byte(s)) 148 } 149 150 func p_help(w http.ResponseWriter, r *http.Request) { 151 if !ipchecker(r) { 152 return 153 } 154 155 fname := "help.html" 156 if len(r.Form["topic"]) > 0 && len(r.Form["topic"][0]) == 4 { 157 for i := 0; i < 4; i++ { 158 if r.Form["topic"][0][i] < 'a' || r.Form["topic"][0][i] > 'z' { 159 goto broken_topic // we only accept 4 locase characters 160 } 161 } 162 fname = "help_" + r.Form["topic"][0] + ".html" 163 } 164 broken_topic: 165 166 page := load_template(fname) 167 write_html_head(w, r) 168 w.Write([]byte(page)) 169 write_html_tail(w) 170 } 171 172 func p_wallet_is_off(w http.ResponseWriter, r *http.Request) { 173 s := load_template("wallet_off.html") 174 write_html_head(w, r) 175 w.Write([]byte(s)) 176 write_html_tail(w) 177 } 178 179 func ServerThread() { 180 fmt.Println("Starting WebUI at", common.CFG.WebUI.Interface) 181 182 http.HandleFunc("/webui/", p_webui) 183 184 http.HandleFunc("/wal", p_wal) 185 http.HandleFunc("/snd", p_snd) 186 http.HandleFunc("/balance.json", json_balance) 187 http.HandleFunc("/payment.zip", dl_payment) 188 http.HandleFunc("/balance.zip", dl_balance) 189 190 http.HandleFunc("/net", p_net) 191 http.HandleFunc("/txs", p_txs) 192 http.HandleFunc("/blocks", p_blocks) 193 http.HandleFunc("/miners", p_miners) 194 http.HandleFunc("/counts", p_counts) 195 http.HandleFunc("/cfg", p_cfg) 196 http.HandleFunc("/help", p_help) 197 198 http.HandleFunc("/txs2s.xml", xml_txs2s) 199 http.HandleFunc("/txsre.xml", xml_txsre) 200 http.HandleFunc("/txw4i.xml", xml_txw4i) 201 http.HandleFunc("/raw_tx", raw_tx) 202 203 http.HandleFunc("/", p_home) 204 http.HandleFunc("/status.json", json_status) 205 http.HandleFunc("/counts.json", json_counts) 206 http.HandleFunc("/system.json", json_system) 207 http.HandleFunc("/bwidth.json", json_bwidth) 208 http.HandleFunc("/txstat.json", json_txstat) 209 http.HandleFunc("/netcon.json", json_netcon) 210 http.HandleFunc("/blocks.json", json_blocks) 211 http.HandleFunc("/peerst.json", json_peerst) 212 http.HandleFunc("/bwchar.json", json_bwchar) 213 http.HandleFunc("/mempool_stats.json", json_mempool_stats) 214 http.HandleFunc("/mempool_fees.json", json_mempool_fees) 215 http.HandleFunc("/blkver.json", json_blkver) 216 http.HandleFunc("/miners.json", json_miners) 217 http.HandleFunc("/blfees.json", json_blfees) 218 http.HandleFunc("/walsta.json", json_wallet_status) 219 220 http.HandleFunc("/mempool_fees.txt", txt_mempool_fees) 221 222 go start_ssl_server() 223 http.ListenAndServe(common.CFG.WebUI.Interface, nil) 224 } 225 226 type null_logger struct { 227 } 228 229 func (nl null_logger) Write(p []byte) (n int, err error) { 230 return len(p), nil 231 } 232 233 func start_ssl_server() { 234 // try to start SSL server... 235 dat, err := ioutil.ReadFile("ssl_cert/ca.crt") 236 if err != nil { 237 println("ssl_cert/ca.crt not found") 238 // no "ca.crt" file - do not start SSL server 239 return 240 } 241 242 port := common.CFG.WebUI.SSLPort 243 if port == 0 { 244 if common.Testnet { 245 port = 14433 246 } else { 247 port = 4433 248 } 249 } 250 ssl_serv_addr := fmt.Sprint(":", port) 251 252 server := &http.Server{ 253 Addr: ssl_serv_addr, 254 TLSConfig: &tls.Config{ 255 ClientAuth: tls.RequireAndVerifyClientCert, 256 }, 257 ErrorLog: log.New(new(null_logger), "", 0), 258 } 259 server.TLSConfig.ClientCAs = x509.NewCertPool() 260 ok := server.TLSConfig.ClientCAs.AppendCertsFromPEM(dat) 261 if !ok { 262 println("AppendCertsFromPEM error") 263 return 264 } 265 266 println("Starting SSL server at", ssl_serv_addr, "...") 267 err = server.ListenAndServeTLS("ssl_cert/server.crt", "ssl_cert/server.key") 268 if err != nil { 269 println(err.Error()) 270 } 271 }