Compare commits

...

2 commits

Author SHA1 Message Date
ed4bbeb608
More incremental progress. 2025-07-27 17:06:28 -07:00
ab3b46713f
Incremental progress 2025-07-27 16:20:52 -07:00
8 changed files with 201 additions and 23 deletions

View file

@ -3,13 +3,21 @@ package args
/* This file manages declaration of, parsing of, and returning copies of /* This file manages declaration of, parsing of, and returning copies of
command line arguments. */ command line arguments. */
import "flag" import (
"flag"
"os"
)
/* Method to trigger flag parsing. Can be called multiple times safely. */ /* Method to trigger flag parsing. Can be called multiple times safely. */
func Parse() { func Parse() {
if (flagsParsed) {return} if (flagsParsed) {return}
flag.Parse(); flag.Parse();
flagsParsed = true; 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 */ /* module-specific variable to avoid re-parsing flags */
var flagsParsed bool = false; var flagsParsed bool = false;
@ -21,3 +29,27 @@ func GetConfLocation() (location string, set bool) {
if confLocation == nil {return "", false} if confLocation == nil {return "", false}
return *confLocation, true; 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", "/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}
return *binaryLocation, true;
}

View file

@ -10,13 +10,14 @@ import (
"strings" "strings"
) )
// import "fmt"
/* Object to handle what is in a JSON config */ /* Object to handle what is in a JSON config */
type Config struct { type Config struct {
configData map[string][]string; configData map[string][]string;
} }
/* Default Config object */
var GlobalConfig * Config;
/* Opens and reads a file at the path */ /* Opens and reads a file at the path */
func NewConfig(filePath string) (newConfig *Config, err error) { func NewConfig(filePath string) (newConfig *Config, err error) {
// Open file // Open file
@ -30,8 +31,7 @@ func NewConfig(filePath string) (newConfig *Config, err error) {
// Read lines into newConfigData // Read lines into newConfigData
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() parts := strings.SplitN(scanner.Text(), " ", 2);
parts := strings.SplitN(line, " ", 2);
if len(parts) != 2 {err = errors.New("Bad config file!"); return;} if len(parts) != 2 {err = errors.New("Bad config file!"); return;}
newConfigData[parts[0]] = append(newConfigData[parts[0]], parts[1]); 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 */ /* Gets a reference copy to the config data */
func (config Config) GetConfigData() map[string][]string {return config.configData;} 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;
}

View file

@ -13,16 +13,8 @@ func splitPath(path string) []string {
/* Attempts to match a request path to a set of whitelisted paths /* Attempts to match a request path to a set of whitelisted paths
@return false for anything other than a valid match */ @return false for anything other than a valid match */
func match(request string, matchTo []string) bool { func match(request string, matchTo string) bool {
requestSplit := splitPath(request) return matchSegments(splitPath(request), splitPath(matchTo));
for _, matchToAttempt := range matchTo {
matchToAttemptSplit := splitPath(matchToAttempt);
if matchSegments(requestSplit, matchToAttemptSplit) {
return true;
}
}
return false;
} }
/* Returns false for anything other than a valid match */ /* Returns false for anything other than a valid match */

64
http/listen.go Normal file
View file

@ -0,0 +1,64 @@
package http
/* This file handles listening to HTTP requests */
import (
"signal-cli-http/conf"
"signal-cli-http/subprocess"
"fmt"
"io"
"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);
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;
}
// OK authentication wise
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)
}

13
main.go
View file

@ -5,8 +5,8 @@ package main
import ( import (
"signal-cli-http/args" "signal-cli-http/args"
"signal-cli-http/conf" "signal-cli-http/conf"
"signal-cli-http/http"
"fmt"
"log" "log"
) )
@ -14,11 +14,14 @@ func main() {
// Read arguments // Read arguments
args.Parse(); args.Parse();
configLocation, confLocationSet := args.GetConfLocation(); 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); log.Default().Print("Reading config value from ", configLocation);
// Set up config // Set up config
config, err := conf.NewConfig(configLocation); conf.GlobalConfig, _ = conf.NewConfig(configLocation);
if err != nil {log.Default().Print("Error reading config: ", err); return;} if conf.GlobalConfig == nil {log.Default().Print("Error reading config"); return}
fmt.Println(config.GetConfigData())
port, portSet := args.GetHTTPPort();
if !portSet {log.Default().Print("No port value!"); return;}
http.StartWebserver(port)
} }

View file

@ -8,3 +8,8 @@ Please also read the following README files for the individual modules:
* [args](args/readme.md) - handles command line arguments. * [args](args/readme.md) - handles command line arguments.
* [conf](conf/readme.md) - handles the config 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.

21
subprocess/command.go Normal file
View file

@ -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
}

44
subprocess/subprocess.go Normal file
View file

@ -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;
}