From d57cb9afa3a7cc5aa7a1e705efcd8118a4a4f7d3 Mon Sep 17 00:00:00 2001 From: George Shaw Date: Tue, 23 Jan 2018 14:38:49 -0600 Subject: [PATCH 1/3] fixed domain router issues and followed HTTP docs in regards to host field --- pkg/gpserver/servehttp.go | 8 +++++++- pkg/router/router.go | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg/gpserver/servehttp.go b/pkg/gpserver/servehttp.go index a4a778a..a1a2530 100644 --- a/pkg/gpserver/servehttp.go +++ b/pkg/gpserver/servehttp.go @@ -6,8 +6,9 @@ import ( "os" "strconv" - "github.com/Ennovar/gPanel/pkg/routing" "strings" + + "github.com/Ennovar/gPanel/pkg/routing" ) func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) { @@ -18,6 +19,11 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) { path = (con.Directory + con.DocumentRoot + path) } + if strings.HasSuffix(path, "throw400") { + http.Error(res, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + if strings.HasSuffix(path, "index.html") { if con.checkAuth(res, req) == true { http.Redirect(res, req, "gPanel.html", http.StatusFound) diff --git a/pkg/router/router.go b/pkg/router/router.go index 86a75c4..ee66754 100644 --- a/pkg/router/router.go +++ b/pkg/router/router.go @@ -6,9 +6,11 @@ import ( "strconv" "time" - "github.com/Ennovar/gPanel/pkg/database" "log" + "strings" "sync" + + "github.com/Ennovar/gPanel/pkg/database" ) type Router struct { @@ -57,14 +59,22 @@ func New() *Router { Addr: "localhost:" + strconv.Itoa(r.Port), Handler: &httputil.ReverseProxy{ Director: func(req *http.Request) { + host := req.Host + if strings.Count(host, ".") == 2 { + host = strings.SplitN(host, ".", 2)[1] //Remove sub-domain + } + + req.Header.Set("Host", req.Host) + req.URL.Scheme = "http" + mutex.Lock() - if d, ok := domainToPort[req.Host]; ok { + if d, ok := domainToPort[host]; ok { mutex.Unlock() - req.Header.Set("Host", req.Host) - req.URL.Scheme = "http" req.URL.Host = "127.0.0.1:" + strconv.Itoa(d) } else { mutex.Unlock() + req.URL.Host = "127.0.0.1:2082" + req.URL.Path = "/throw400" } }, }, @@ -78,7 +88,7 @@ func New() *Router { go func() { for { select { - case <- ticker.C: + case <-ticker.C: if !RefreshMap() { ticker.Stop() log.Fatal("Error refreshing domain/bundle pairing for router") From f802fd8eac3652c80fef6e4d125e0225dede7cb2 Mon Sep 17 00:00:00 2001 From: George Shaw Date: Tue, 23 Jan 2018 15:35:48 -0600 Subject: [PATCH 2/3] front end interface for adding/removing/listing subdomains for accounts is in place --- .../domains_subdomains/subdomain_add.js | 30 ++++++++++++ .../subdomain_management.js | 37 ++++++++++++++ .../domains_subdomains/subdomain_remove.js | 29 +++++++++++ account/gPanel.html | 46 +++++++++++++++++ pkg/api/subdomain/add.go | 48 ++++++++++++++++++ pkg/api/subdomain/list.go | 49 +++++++++++++++++++ pkg/api/subdomain/remove.go | 47 ++++++++++++++++++ pkg/database/database.go | 10 +++- pkg/database/subdomains.go | 30 ++++++++++++ pkg/gpaccount/apihandler.go | 7 +++ 10 files changed, 331 insertions(+), 2 deletions(-) create mode 100644 account/assets/js/panelHandlers/domains_subdomains/subdomain_add.js create mode 100644 account/assets/js/panelHandlers/domains_subdomains/subdomain_management.js create mode 100644 account/assets/js/panelHandlers/domains_subdomains/subdomain_remove.js create mode 100644 pkg/api/subdomain/add.go create mode 100644 pkg/api/subdomain/list.go create mode 100644 pkg/api/subdomain/remove.go create mode 100644 pkg/database/subdomains.go diff --git a/account/assets/js/panelHandlers/domains_subdomains/subdomain_add.js b/account/assets/js/panelHandlers/domains_subdomains/subdomain_add.js new file mode 100644 index 0000000..bf5bd5b --- /dev/null +++ b/account/assets/js/panelHandlers/domains_subdomains/subdomain_add.js @@ -0,0 +1,30 @@ +jQuery('._js_add-subdomain-form').on('submit', function(e){ + e.preventDefault(); + + if(jQuery('#addSubdomain') && jQuery('#addSubdomain').val() && jQuery('#subdomainRoot') && jQuery('#subdomainRoot').val()) { + var requestData = {}; + requestData["name"] = jQuery('#addSubdomain').val(); + requestData["root"] = jQuery('#subdomainRoot').val(); + + var xhr = new XMLHttpRequest(); + xhr.open(jQuery(this).attr('method'), jQuery(this).attr('action'), true); + xhr.send(JSON.stringify(requestData)); + + xhr.onloadend = function() { + if(xhr.status == 204) { + ListSubdomains(); + } + else { + if(xhr.response != undefined && xhr.response.length != 0) { + alert('Error: ' + xhr.status); + } + else { + alert('An error has occurred. If problem persists please contact your community administrator.'); + } + } + } + } + else { + alert('All fields must be filled out to submit this form.'); + } +}); \ No newline at end of file diff --git a/account/assets/js/panelHandlers/domains_subdomains/subdomain_management.js b/account/assets/js/panelHandlers/domains_subdomains/subdomain_management.js new file mode 100644 index 0000000..9ced8a2 --- /dev/null +++ b/account/assets/js/panelHandlers/domains_subdomains/subdomain_management.js @@ -0,0 +1,37 @@ +var subdomainModal = jQuery('.subdomain-management-modal'); + +jQuery('._js_subdomain-management').on('click', function(e){ + e.preventDefault(); + + ListSubdomains(); + subdomainModal.modal('show'); +}); + +function ListSubdomains() { + var list = jQuery('._js_registered-subdomains'); + + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'api/subdomain/list', true); + xhr.send(); + + xhr.onloadend = function() { + list.html(''); + if(xhr.status == 200) { + jsonResponse = JSON.parse(xhr.response) + jQuery.each(jsonResponse, function(k, v) { + list.append('

'+k+'

Root: '+v.root+'
'); + }); + } + else if(xhr.status == 204) { + list.html('

No registered subdomains exist for this account.

'); + } + else { + if(xhr.response != undefined && xhr.response.length != 0) { + alert('Error: ' + xhr.status); + } + else { + alert('An error has occurred. If problem persists please contact your community administrator.'); + } + } + } +} \ No newline at end of file diff --git a/account/assets/js/panelHandlers/domains_subdomains/subdomain_remove.js b/account/assets/js/panelHandlers/domains_subdomains/subdomain_remove.js new file mode 100644 index 0000000..e67175e --- /dev/null +++ b/account/assets/js/panelHandlers/domains_subdomains/subdomain_remove.js @@ -0,0 +1,29 @@ +jQuery(document).on('click', '._js_delete-registered-subdomain', function(e){ + e.preventDefault(); + + var subdomain = jQuery(this).attr('data'); + var ensure = confirm("Are you sure you want to delete the subdomain \""+ subdomain +"\" from your account?"); + + if(ensure) { + var requestData = {}; + requestData["name"] = subdomain; + + var xhr = new XMLHttpRequest(); + xhr.open('DELETE', 'api/subdomain/remove', true); + xhr.send(JSON.stringify(requestData)); + + xhr.onloadend = function () { + if (xhr.status == 204) { + ListSubdomains(); + } + else { + if (xhr.response != undefined && xhr.response.length != 0) { + alert('Error: ' + xhr.status); + } + else { + alert('An error has occurred. If problem persists please contact your community administrator.'); + } + } + } + } +}); \ No newline at end of file diff --git a/account/gPanel.html b/account/gPanel.html index 77924d8..cbd9b3a 100644 --- a/account/gPanel.html +++ b/account/gPanel.html @@ -349,6 +349,48 @@ + + +
@@ -447,6 +489,7 @@
View registered domains, register new domains, and set up or manage existing document roots for sub-domains.
+
@@ -501,6 +544,9 @@ + + + diff --git a/pkg/api/subdomain/add.go b/pkg/api/subdomain/add.go new file mode 100644 index 0000000..bc25c81 --- /dev/null +++ b/pkg/api/subdomain/add.go @@ -0,0 +1,48 @@ +package subdomain + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/Ennovar/gPanel/pkg/database" +) + +func Add(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool { + if req.Method != "POST" { + logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed)) + http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return false + } + + var requestData struct { + Name string `json:"name"` + Root string `json:"root"` + } + + err := json.NewDecoder(req.Body).Decode(&requestData) + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusBadRequest) + return false + } + + ds, err := database.Open(dir + database.DB_MAIN) + if err != nil || ds == nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + defer ds.Close() + + err = ds.Put(database.BUCKET_SUBDOMAINS, []byte(requestData.Name), database.StructSubdomain{Root: requestData.Root}) + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + + res.WriteHeader(http.StatusNoContent) + return true +} diff --git a/pkg/api/subdomain/list.go b/pkg/api/subdomain/list.go new file mode 100644 index 0000000..c9c1119 --- /dev/null +++ b/pkg/api/subdomain/list.go @@ -0,0 +1,49 @@ +package subdomain + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/Ennovar/gPanel/pkg/database" +) + +func List(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool { + if req.Method != "GET" { + logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed)) + http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return false + } + + ds, err := database.Open(dir + database.DB_MAIN) + if err != nil || ds == nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + defer ds.Close() + + subdomains, err := ds.ListSubdomains() + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + + if len(subdomains) > 0 { + b, err := json.Marshal(subdomains) + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + + res.WriteHeader(http.StatusOK) + res.Write(b) + return true + } + + res.WriteHeader(http.StatusNoContent) + return true +} diff --git a/pkg/api/subdomain/remove.go b/pkg/api/subdomain/remove.go new file mode 100644 index 0000000..9e6e153 --- /dev/null +++ b/pkg/api/subdomain/remove.go @@ -0,0 +1,47 @@ +package subdomain + +import ( + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/Ennovar/gPanel/pkg/database" +) + +func Remove(res http.ResponseWriter, req *http.Request, logger *log.Logger, dir string) bool { + if req.Method != "DELETE" { + logger.Println(req.URL.Path + "::" + req.Method + "::" + strconv.Itoa(http.StatusMethodNotAllowed) + "::" + http.StatusText(http.StatusMethodNotAllowed)) + http.Error(res, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) + return false + } + + var requestData struct { + Name string `json:"name"` + } + + err := json.NewDecoder(req.Body).Decode(&requestData) + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusBadRequest) + return false + } + + ds, err := database.Open(dir + database.DB_MAIN) + if err != nil || ds == nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + defer ds.Close() + + err = ds.Delete(database.BUCKET_SUBDOMAINS, []byte(requestData.Name)) + if err != nil { + logger.Println(req.URL.Path + "::" + err.Error()) + http.Error(res, err.Error(), http.StatusInternalServerError) + return false + } + + res.WriteHeader(http.StatusNoContent) + return true +} diff --git a/pkg/database/database.go b/pkg/database/database.go index 434f16b..0b4961d 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -14,7 +14,7 @@ import ( const ( DB_MAIN = "datastore.db" DB_SETTINGS = "settings.db" - DB_DOMAINS = "domains.db" + DB_DOMAINS = "domains.db" ) // Bucket constants @@ -23,9 +23,10 @@ const ( BUCKET_USERS = "users" BUCKET_PORTS = "ports" BUCKET_FILTERED_IPS = "filtered_ips" + BUCKET_SUBDOMAINS = "subdomains" // DB_SETTINGS BUCKETS - BUCKET_GENERAL = "general" + BUCKET_GENERAL = "general" BUCKET_NAMESERVERS = "nameservers" // DB_DOMAINS BUCKETS @@ -71,6 +72,11 @@ func Open(filepath string) (*Datastore, error) { if err != nil { return err } + + _, err = tx.CreateBucketIfNotExists([]byte(BUCKET_SUBDOMAINS)) + if err != nil { + return err + } } if strings.HasSuffix(filepath, DB_SETTINGS) { diff --git a/pkg/database/subdomains.go b/pkg/database/subdomains.go new file mode 100644 index 0000000..0bcc9aa --- /dev/null +++ b/pkg/database/subdomains.go @@ -0,0 +1,30 @@ +package database + +import ( + "encoding/json" + + "github.com/boltdb/bolt" +) + +type StructSubdomain struct { + Root string `json:"root"` +} + +func (ds *Datastore) ListSubdomains() (map[string]StructSubdomain, error) { + filtered := make(map[string]StructSubdomain) + var holder StructSubdomain + + ds.handle.View(func(tx *bolt.Tx) error { + b := tx.Bucket([]byte(BUCKET_SUBDOMAINS)) + c := b.Cursor() + + for k, v := c.First(); k != nil; k, v = c.Next() { + json.Unmarshal(v, &holder) + filtered[string(k)] = holder + } + + return nil + }) + + return filtered, nil +} diff --git a/pkg/gpaccount/apihandler.go b/pkg/gpaccount/apihandler.go index f345e04..8bc52c4 100644 --- a/pkg/gpaccount/apihandler.go +++ b/pkg/gpaccount/apihandler.go @@ -11,6 +11,7 @@ import ( "github.com/Ennovar/gPanel/pkg/api/server" "github.com/Ennovar/gPanel/pkg/api/settings" "github.com/Ennovar/gPanel/pkg/api/ssh" + "github.com/Ennovar/gPanel/pkg/api/subdomain" "github.com/Ennovar/gPanel/pkg/api/user" ) @@ -74,6 +75,12 @@ func (con *Controller) apiHandler(res http.ResponseWriter, req *http.Request) (b return true, ssh.DeleteKey(res, req, con.APILogger) case "/ssh/getkeys": return true, ssh.GetKeys(res, req, con.APILogger) + case "/subdomain/list": + return true, subdomain.List(res, req, con.APILogger, con.Directory) + case "/subdomain/add": + return true, subdomain.Add(res, req, con.APILogger, con.Directory) + case "/subdomain/remove": + return true, subdomain.Remove(res, req, con.APILogger, con.Directory) default: return false, false } From 05ef046e5567e6986f56db4b2692cf304eba0be6 Mon Sep 17 00:00:00 2001 From: George Shaw Date: Tue, 23 Jan 2018 15:50:18 -0600 Subject: [PATCH 3/3] subdomains now work --- pkg/gpaccount/gpaccount.go | 2 +- pkg/public/public.go | 8 ++++--- pkg/public/servehttp.go | 43 +++++++++++++++++++++++++++++++++++--- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pkg/gpaccount/gpaccount.go b/pkg/gpaccount/gpaccount.go index 6380ba5..e786291 100644 --- a/pkg/gpaccount/gpaccount.go +++ b/pkg/gpaccount/gpaccount.go @@ -42,7 +42,7 @@ func New(dir, name string, accPort, pubPort int) *Controller { DocumentRoot: "account/", Name: name, Port: accPort, - Public: public.New("/home/"+name+"/", pubPort), + Public: public.New("/home/"+name+"/", dir, pubPort), GracefulShutdownTimeout: 5 * time.Second, Status: 0, AccountLogger: accountLogger, diff --git a/pkg/public/public.go b/pkg/public/public.go index 3fffde7..19c01cf 100644 --- a/pkg/public/public.go +++ b/pkg/public/public.go @@ -12,6 +12,7 @@ import ( type Controller struct { Directory string + AccountDirectory string Port int GracefulShutdownTimeout time.Duration Status int @@ -23,15 +24,16 @@ var controller Controller var server http.Server // New function returns a new PublicWeb type. -func New(dir string, port int) *Controller { +func New(dir, accountDir string, port int) *Controller { ph, lh, err := getLogHandles(dir) if err != nil { log.Fatalf("Error trying to start logging instances within %v: %v", dir, err.Error()) } controller = Controller{ - Directory: dir, - Port: port, + Directory: dir, + AccountDirectory: accountDir, + Port: port, GracefulShutdownTimeout: 5 * time.Second, Status: 0, PublicLogger: ph, diff --git a/pkg/public/servehttp.go b/pkg/public/servehttp.go index 368caef..5d0067b 100644 --- a/pkg/public/servehttp.go +++ b/pkg/public/servehttp.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/Ennovar/gPanel/pkg/database" "github.com/Ennovar/gPanel/pkg/routing" ) @@ -92,10 +93,46 @@ func (con *Controller) ServeHTTP(res http.ResponseWriter, req *http.Request) { } path := req.URL.Path[1:] - if len(path) == 0 { - path = con.Directory + "document_root/" + "index.html" + if strings.HasPrefix(req.Host, "www") { + if len(path) == 0 { + path = con.Directory + "document_root/" + "index.html" + } else { + path = con.Directory + "document_root/" + path + } } else { - path = con.Directory + "document_root/" + path + if strings.Count(req.Host, ".") == 2 { + subdomain := strings.SplitN(req.Host, ".", 2)[0] //Remove sub-domain + + ds, err := database.Open(con.AccountDirectory + database.DB_MAIN) + if err != nil || ds == nil { + con.PublicLogger.Println(path + "::" + strconv.Itoa(http.StatusInternalServerError) + "::" + err.Error()) + routing.HttpThrowStatus(http.StatusInternalServerError, res) + return + } + + var sdRoot database.StructSubdomain + + err = ds.Get(database.BUCKET_SUBDOMAINS, []byte(subdomain), &sdRoot) + if err != nil { + con.PublicLogger.Println(path + "::" + strconv.Itoa(http.StatusInternalServerError) + "::" + err.Error()) + routing.HttpThrowStatus(http.StatusInternalServerError, res) + return + } + + _ = ds.Close() + + if len(path) == 0 { + path = con.Directory + "document_root/" + sdRoot.Root + "/index.html" + } else { + path = con.Directory + "document_root/" + sdRoot.Root + "/" + path + } + } else { + if len(path) == 0 { + path = con.Directory + "document_root/" + "index.html" + } else { + path = con.Directory + "document_root/" + path + } + } } contentType, err := routing.GetContentType(path)