From ed942f3e31a29ec60440ef66b6839a8b828bcfab Mon Sep 17 00:00:00 2001
From: Simon Zolin <s.zolin@adguard.com>
Date: Tue, 19 Mar 2019 18:47:22 +0300
Subject: [PATCH] + control: /clients: get the list of clients' IP addresses
 and names from /etc/hosts

---
 clients.go           | 92 ++++++++++++++++++++++++++++++++++++++++++++
 control.go           |  1 +
 openapi/openapi.yaml | 36 +++++++++++++++++
 3 files changed, 129 insertions(+)
 create mode 100644 clients.go

diff --git a/clients.go b/clients.go
new file mode 100644
index 00000000..87a5c3fe
--- /dev/null
+++ b/clients.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"runtime"
+	"strings"
+
+	"github.com/AdguardTeam/golibs/log"
+)
+
+// Client information
+type Client struct {
+	IP   string
+	Name string
+	//Source source // Hosts file / User settings / DHCP
+}
+
+type clientJSON struct {
+	IP   string `json:"ip"`
+	Name string `json:"name"`
+}
+
+var clients []Client
+var clientsFilled bool
+
+// Parse system 'hosts' file and fill clients array
+func fillClientInfo() {
+	hostsFn := "/etc/hosts"
+	if runtime.GOOS == "windows" {
+		hostsFn = os.ExpandEnv("$SystemRoot\\system32\\drivers\\etc\\hosts")
+	}
+
+	d, e := ioutil.ReadFile(hostsFn)
+	if e != nil {
+		log.Info("Can't read file %s: %v", hostsFn, e)
+		return
+	}
+
+	lines := strings.Split(string(d), "\n")
+	for _, ln := range lines {
+		ln = strings.TrimSpace(ln)
+		if len(ln) == 0 || ln[0] == '#' {
+			continue
+		}
+
+		fields := strings.Fields(ln)
+		if len(fields) < 2 {
+			continue
+		}
+
+		var c Client
+		c.IP = fields[0]
+		c.Name = fields[1]
+		clients = append(clients, c)
+		log.Tracef("%s -> %s", c.IP, c.Name)
+	}
+
+	log.Info("Added %d client aliases from %s", len(clients), hostsFn)
+	clientsFilled = true
+}
+
+// respond with information about configured clients
+func handleGetClients(w http.ResponseWriter, r *http.Request) {
+	log.Tracef("%s %v", r.Method, r.URL)
+
+	if !clientsFilled {
+		fillClientInfo()
+	}
+
+	data := []clientJSON{}
+	for _, c := range clients {
+		cj := clientJSON{
+			IP:   c.IP,
+			Name: c.Name,
+		}
+		data = append(data, cj)
+	}
+	w.Header().Set("Content-Type", "application/json")
+	e := json.NewEncoder(w).Encode(data)
+	if e != nil {
+		httpError(w, http.StatusInternalServerError, "Failed to encode to json: %v", e)
+		return
+	}
+}
+
+// RegisterClientsHandlers registers HTTP handlers
+func RegisterClientsHandlers() {
+	http.HandleFunc("/control/clients", postInstall(optionalAuth(ensureGET(handleGetClients))))
+}
diff --git a/control.go b/control.go
index be0d61b2..39fe1c66 100644
--- a/control.go
+++ b/control.go
@@ -1063,6 +1063,7 @@ func registerControlHandlers() {
 	http.HandleFunc("/control/dhcp/find_active_dhcp", postInstall(optionalAuth(ensurePOST(handleDHCPFindActiveServer))))
 
 	RegisterTLSHandlers()
+	RegisterClientsHandlers()
 
 	http.HandleFunc("/dns-query", postInstall(handleDOH))
 }
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index d8b1072c..569d94d7 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -39,6 +39,9 @@ tags:
     -
         name: dhcp
         description: 'Built-in DHCP server controls'
+    -
+        name: clients
+        description: 'Clients list operations'
     -
         name: install
         description: 'First-time install configuration handlers'
@@ -668,6 +671,22 @@ paths:
                         application/json:
                             enabled: false
 
+    # --------------------------------------------------
+    # Clients list methods
+    # --------------------------------------------------
+
+    /clients:
+        get:
+            tags:
+                - clients
+            operationId: clientsStatus
+            summary: 'Get information about configured clients'
+            responses:
+                200:
+                    description: OK
+                    schema:
+                        $ref: "#/definitions/Clients"
+
     # --------------------------------------------------
     # I18N methods
     # --------------------------------------------------
@@ -1317,6 +1336,23 @@ definitions:
                 description: "Network interfaces dictionary (key is the interface name)"
                 additionalProperties:
                     $ref: "#/definitions/NetInterface"
+    Client:
+        type: "object"
+        description: "Client information"
+        properties:
+            ip:
+                type: "string"
+                description: "IP address"
+                example: "127.0.0.1"
+            name:
+                type: "string"
+                description: "Name"
+                example: "localhost"
+    Clients:
+        type: "array"
+        items:
+            $ref: "#/definitions/Client"
+        description: "Clients array"
     InitialConfiguration:
         type: "object"
         description: "AdGuard Home initial configuration (for the first-install wizard)"