From ab3b46713f94f0b5c25f03e867a93c49bcb25239 Mon Sep 17 00:00:00 2001 From: 50501AZ Webmaster Date: Sun, 27 Jul 2025 16:20:52 -0700 Subject: [PATCH 1/2] Incremental progress --- args/args.go | 24 ++++++++++++++++++++++++ conf/conf.go | 27 ++++++++++++++++++++++----- conf/regex.go | 12 ++---------- http/listen.go | 36 ++++++++++++++++++++++++++++++++++++ main.go | 15 +++++++++------ readme.md | 7 ++++++- 6 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 http/listen.go diff --git a/args/args.go b/args/args.go index a859ea2..a495f0b 100644 --- a/args/args.go +++ b/args/args.go @@ -20,4 +20,28 @@ var confLocation = flag.String("conf", "./config.txt", "Config file to read from func GetConfLocation() (location string, set bool) { if confLocation == nil {return "", false} return *confLocation, true; +} + +/* TCP port to bind to */ +var httpPort = flag.Int("port", 11938, "Port number to bind to") +/* @return set boolean will be true if argument is not nil */ +func GetHTTPPort() (port int, set bool) { + if httpPort == nil {return -1, false} + return *httpPort, true; +} + +/* Listen on a UNIX socket */ +var socketLocation = flag.String("socket", "", "Location of UNIX socket to listen on. Setting will disable TCP.") +/* @return set boolean will be true if argument is not nil */ +func GetSocketLocation() (port string, set bool) { + if socketLocation == nil {return "", false} + return *socketLocation, true; +} + +/* Where the signal-cli binary is */ +var binaryLocation = flag.String("binary", "", "Location of the signal-cli binary.") +/* @return set boolean will be true if argument is not nil */ +func GetBinaryLocation() (port string, set bool) { + if binaryLocation == nil {return "", false} + return *binaryLocation, true; } \ No newline at end of file diff --git a/conf/conf.go b/conf/conf.go index 5356b37..fb81fb7 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -10,13 +10,14 @@ import ( "strings" ) -// import "fmt" - /* Object to handle what is in a JSON config */ type Config struct { configData map[string][]string; } +/* Default Config object */ +var GlobalConfig * Config; + /* Opens and reads a file at the path */ func NewConfig(filePath string) (newConfig *Config, err error) { // Open file @@ -30,8 +31,7 @@ func NewConfig(filePath string) (newConfig *Config, err error) { // Read lines into newConfigData scanner := bufio.NewScanner(file) for scanner.Scan() { - line := scanner.Text() - parts := strings.SplitN(line, " ", 2); + parts := strings.SplitN(scanner.Text(), " ", 2); if len(parts) != 2 {err = errors.New("Bad config file!"); return;} newConfigData[parts[0]] = append(newConfigData[parts[0]], parts[1]); } @@ -41,4 +41,21 @@ func NewConfig(filePath string) (newConfig *Config, err error) { } /* Gets a reference copy to the config data */ -func (config Config) GetConfigData() map[string][]string {return config.configData;} \ No newline at end of file +func (config * Config) GetConfigData() map[string][]string { + return config.configData; +} + +/* Returns if a bearer key is authorized for the path in this Config object + @return false for any situation that isn't a valid match */ +func (config * Config) ValidateBearerKey(bearerKey string, request string) bool { + paths, exists := config.configData[bearerKey]; + if !exists {return false} + + for _, matchTo := range paths { + if match(request, matchTo) { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/conf/regex.go b/conf/regex.go index 4a1fb0a..32524df 100644 --- a/conf/regex.go +++ b/conf/regex.go @@ -13,16 +13,8 @@ func splitPath(path string) []string { /* Attempts to match a request path to a set of whitelisted paths @return false for anything other than a valid match */ -func match(request string, matchTo []string) bool { - requestSplit := splitPath(request) - for _, matchToAttempt := range matchTo { - matchToAttemptSplit := splitPath(matchToAttempt); - if matchSegments(requestSplit, matchToAttemptSplit) { - return true; - } - } - - return false; +func match(request string, matchTo string) bool { + return matchSegments(splitPath(request), splitPath(matchTo)); } /* Returns false for anything other than a valid match */ diff --git a/http/listen.go b/http/listen.go new file mode 100644 index 0000000..3f372eb --- /dev/null +++ b/http/listen.go @@ -0,0 +1,36 @@ +package http + +/* This file handles listening to HTTP requests */ + +import ( + "signal-cli-http/conf" + + "fmt" + "log" + "net/http" +) + +func StartWebserver(port int) { + http.HandleFunc("/", getRoot) + + err := http.ListenAndServe(":"+fmt.Sprint(port), nil) + fmt.Println(err) +} + +func getRoot(w http.ResponseWriter, r *http.Request) { + // Check that Authentication header exists + authArr, ok := r.Header["Authentication"] + if (!ok) || (len(authArr) == 0) {w.WriteHeader(400); return} + bearer := authArr[0]; + + // Check that the request is allowed for the path + if !conf.GlobalConfig.ValidateBearerKey(bearer, r.URL.Path) { + w.WriteHeader(403); + return; + } + + log.Default().Print("HTTP Request: ", bearer, " " , r.URL.Path) + + // OK authentication wise + w.WriteHeader(200); +} \ No newline at end of file diff --git a/main.go b/main.go index 1e18720..006eec7 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,8 @@ package main import ( "signal-cli-http/args" "signal-cli-http/conf" - - "fmt" + "signal-cli-http/http" + "log" ) @@ -14,11 +14,14 @@ func main() { // Read arguments args.Parse(); configLocation, confLocationSet := args.GetConfLocation(); - if !confLocationSet {log.Default().Print("No config value!"); return;} + if !confLocationSet {log.Default().Print("No config value!"); return} log.Default().Print("Reading config value from ", configLocation); // Set up config - config, err := conf.NewConfig(configLocation); - if err != nil {log.Default().Print("Error reading config: ", err); return;} - fmt.Println(config.GetConfigData()) + conf.GlobalConfig, _ = conf.NewConfig(configLocation); + if conf.GlobalConfig == nil {log.Default().Print("Error reading config"); return} + + port, portSet := args.GetHTTPPort(); + if !portSet {log.Default().Print("No port value!"); return;} + http.StartWebserver(port) } \ No newline at end of file diff --git a/readme.md b/readme.md index ace0a89..36814b9 100644 --- a/readme.md +++ b/readme.md @@ -7,4 +7,9 @@ Very simple HTTP frontend to [signal-cli](https://github.com/AsamK/signal-cli). Please also read the following README files for the individual modules: * [args](args/readme.md) - handles command line arguments. -* [conf](conf/readme.md) - handles the config file. \ No newline at end of file +* [conf](conf/readme.md) - handles the config file. + +Too be implemented: + +* subprocess - handles running the binaries which communicate with the daemon. +* web - handles the incoming http requests. \ No newline at end of file From ed4bbeb6083eb68c5c592c753e1f2758ddc11295 Mon Sep 17 00:00:00 2001 From: 50501AZ Webmaster Date: Sun, 27 Jul 2025 17:06:28 -0700 Subject: [PATCH 2/2] More incremental progress. --- args/args.go | 12 +++++++++-- http/listen.go | 36 ++++++++++++++++++++++++++++---- subprocess/command.go | 21 +++++++++++++++++++ subprocess/subprocess.go | 44 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 subprocess/command.go create mode 100644 subprocess/subprocess.go diff --git a/args/args.go b/args/args.go index a495f0b..5403154 100644 --- a/args/args.go +++ b/args/args.go @@ -3,13 +3,21 @@ package args /* This file manages declaration of, parsing of, and returning copies of command line arguments. */ -import "flag" +import ( + "flag" + "os" +) /* Method to trigger flag parsing. Can be called multiple times safely. */ func Parse() { if (flagsParsed) {return} flag.Parse(); flagsParsed = true; + + // Process DBUS socket location + if socketLocation == nil || *socketLocation == "" { + *socketLocation = os.Getenv("DBUS_SYSTEM_BUS_ADDRESS"); + } } /* module-specific variable to avoid re-parsing flags */ var flagsParsed bool = false; @@ -39,7 +47,7 @@ func GetSocketLocation() (port string, set bool) { } /* Where the signal-cli binary is */ -var binaryLocation = flag.String("binary", "", "Location of the signal-cli binary.") +var binaryLocation = flag.String("binary", "/usr/local/bin/signal-cli", "Location of the signal-cli binary.") /* @return set boolean will be true if argument is not nil */ func GetBinaryLocation() (port string, set bool) { if binaryLocation == nil {return "", false} diff --git a/http/listen.go b/http/listen.go index 3f372eb..1a0934e 100644 --- a/http/listen.go +++ b/http/listen.go @@ -4,8 +4,10 @@ package http import ( "signal-cli-http/conf" + "signal-cli-http/subprocess" "fmt" + "io" "log" "net/http" ) @@ -20,17 +22,43 @@ func StartWebserver(port int) { func getRoot(w http.ResponseWriter, r *http.Request) { // Check that Authentication header exists authArr, ok := r.Header["Authentication"] - if (!ok) || (len(authArr) == 0) {w.WriteHeader(400); return} + if (!ok) || (len(authArr) == 0) { + w.WriteHeader(400); + w.Write([]byte("Authentication header missing\n")) + return; + } bearer := authArr[0]; // Check that the request is allowed for the path if !conf.GlobalConfig.ValidateBearerKey(bearer, r.URL.Path) { w.WriteHeader(403); + w.Write([]byte("Bearer key not whitelisted for this path\n")) return; } - log.Default().Print("HTTP Request: ", bearer, " " , r.URL.Path) - // OK authentication wise - w.WriteHeader(200); + + body, err := io.ReadAll(r.Body) + if err != nil { + w.WriteHeader(500); + w.Write([]byte("Error reading body\n")) + return; + } + + // Call subprocess + status, bodyContent, err := subprocess.Run(r.URL.Path, body) + + // Error + if err != nil { + w.WriteHeader(500); + w.Write([]byte("Internal server error: " + err.Error() + "\n")); + return + } + + // Respond to client with status + w.WriteHeader(status); + w.Write(bodyContent); + + // Log the request + log.Default().Print("HTTP Request: ", bearer, " " , r.URL.Path, " ", status) } \ No newline at end of file diff --git a/subprocess/command.go b/subprocess/command.go new file mode 100644 index 0000000..2b53020 --- /dev/null +++ b/subprocess/command.go @@ -0,0 +1,21 @@ +package subprocess + +/* This file manages creating the command line arguments to the subprocess */ + +/* Method which module http calls to create the subprocess */ +func Run(path string, body []byte) (status int, bodyContents []byte, err error) { + arguments := getArguments(path, body); + + // Don't know what to do with this request + if arguments == nil {return 404, []byte("Unknown request\n"), nil;} + + // Call subprocess + status, bodyContents, err = runCommand(arguments); + + return +} + +/* Converts a request into the correct binary arguments */ +func getArguments(path string, body []byte) []string { + return nil // For now +} \ No newline at end of file diff --git a/subprocess/subprocess.go b/subprocess/subprocess.go new file mode 100644 index 0000000..a1ea05f --- /dev/null +++ b/subprocess/subprocess.go @@ -0,0 +1,44 @@ +package subprocess + +import ( + "signal-cli-http/args" + + "errors" + "os/exec" + "strings" +) + +/* This file manages calling signal-cli */ + +/* Runs the command */ +func runCommand(arguments []string) (returnStatus int, bodyContent []byte, err error) { + // Get binary location + binary, ok := args.GetBinaryLocation(); + if !ok { + err = errors.New("Binary cannot be found!"); + return; + } + + // Create command using binary location and arguments + command := exec.Command(binary, strings.Join(arguments, " ")); + + // Duplicate pointer into command.Stdout + //command.Stdout = &bodyContent; + + // Run the command + err = command.Run(); + if err != nil {return} + + // Extract exit code if possible + if exitError, ok := err.(*exec.ExitError); ok { + returnStatus = exitError.ExitCode(); + } else { + err = errors.New("Cannot get exit code!"); + } + + // Get output + bodyContent, err = command.Output(); + + // Named return values allow this + return; +} \ No newline at end of file