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  }