From 59e6a16591ffe2d97553b95f1e6842b9875176e0 Mon Sep 17 00:00:00 2001 From: Bernhard B Date: Thu, 30 May 2019 19:50:54 +0200 Subject: [PATCH] initial commit --- .gitignore | 2 + Dockerfile | 36 ++++++++++ README.md | 56 +++++++++++++++ docker-compose.yml | 9 +++ src/main.go | 166 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 src/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..336e0a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +signal-cli-config +src/main diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..576b9f9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,36 @@ +FROM ubuntu:latest + +RUN apt-get update && apt-get install -y wget default-jre software-properties-common git + +RUN add-apt-repository ppa:gophers/archive +RUN apt-get update +RUN apt-get install -y golang-1.11-go + + +# ARM +#RUN wget -P /tmp/ https://dl.google.com/go/go1.12.5.linux-armv6l.tar.gz \ +# && tar -C /usr/local -xzf /tmp/go1.12.5.linux-armv6l.tar.gz \ +# && rm -rf /tmp/go1.12.5.linux-armv6l.tar.gz + +#RUN wget -P /tmp/ https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz \ +# && tar -C /user/local -xzf /tmp/go1.12.5.linux-amd64.tar.gz \ +# && rm -rf /tmp/go1.12.5.linux-amd64.tar.gz + +ENV PATH /usr/lib/go-1.11/bin/:$PATH + + +RUN wget -P /tmp/ https://github.com/AsamK/signal-cli/releases/download/v0.6.2/signal-cli-0.6.2.tar.gz \ + && tar -C /usr/bin -xzf /tmp/signal-cli-0.6.2.tar.gz \ + && rm -rf /tmp/signal-cli-0.6.2.tar.gz + + +RUN mkdir -p /signal-cli-config/ +RUN mkdir -p /home/.local/share/signal-cli +COPY src/ /tmp/signal-cli-rest-api-src +RUN cd /tmp/signal-cli-rest-api-src && go get -d ./... && go build main.go + +ENV PATH /tmp/signal-cli-rest-api-src/:/usr/bin/signal-cli-0.6.2/bin/:$PATH + +EXPOSE 8080 + +ENTRYPOINT ["main"] \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..91f096d --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Dockerized signal-cli REST API + +This project creates a small dockerized REST API around signal-cli(https://github.com/AsamK/signal-cli). + + +At the moment, the following functionality is exposed via REST: + +* Register a number +* Verify the number using the code received via SMS +* Send message to multiple recipients + + +## Examples + +Sample `docker-compose.yml`file: + +``` +version: "3" +services: + signal-cli-rest-api: + build: "." + ports: + - "8080:8080" #map docker port 8080 to host port 8080. + network_mode: "host" + volumes: + - "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered + +``` + +Sample REST API calls: + +* Register a number (with SMS verification) +```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/register/'``` + +e.g: +```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291'``` + +* Verify the number using the code received via SMS +```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register//verify/'``` + +e.g: +```curl -X POST -H "Content-Type: application/json" 'http://127.0.0.1:8080/v1/register/+431212131491291/verify/123-456'``` + +* Send message to multiple recipients + +```curl -X POST -H "Content-Type: application/json" -d '{"message": "", "number": "", "recipients": ["", ""]}' 'http://127.0.0.1:8080/v1/send'``` + +e.g: + +```curl -X POST -H "Content-Type: application/json" -d '{"message": "Hello World!", "number": "+431212131491291", "recipients": ["+4354546464654", "+4912812812121"]}' 'http://127.0.0.1:8080/v1/send'``` + +* Send a message (+ base64 encoded attachment) to multiple recipients + +```curl -X POST -H "Content-Type: application/json" -d '{"message": "", "base64_attachment": "", "number": "", "recipients": ["", ""]}' 'http://127.0.0.1:8080/v1/send'``` + +In case you need more functionality, please create a pull request! diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..19a700b --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" +services: + signal-cli-rest-api: + build: "." + ports: + - "8080:8080" #map docker port 8080 to host port 8080. + network_mode: "host" + volumes: + - "./signal-cli-config:/home/.local/share/signal-cli" #map "signal-cli-config" folder on host system into docker container. the folder contains the password and cryptographic keys when a new number is registered \ No newline at end of file diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..5d6d050 --- /dev/null +++ b/src/main.go @@ -0,0 +1,166 @@ +package main + +import ( + log "github.com/sirupsen/logrus" + "github.com/satori/go.uuid" + "github.com/gin-gonic/gin" + "github.com/h2non/filetype" + "os/exec" + "time" + "errors" + "flag" + "bytes" + "os" + "encoding/base64" + //"strings" +) + +func runSignalCli(args []string) error { + cmd := exec.Command("signal-cli", args...) + var errBuffer bytes.Buffer + cmd.Stderr = &errBuffer + + err := cmd.Start() + if err != nil { + return err + } + + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + select { + case <-time.After(60 * time.Second): + err := cmd.Process.Kill() + if err != nil { + return err + } + return errors.New("process killed as timeout reached") + case err := <-done: + if err != nil { + return errors.New(errBuffer.String()) + } + } + return nil +} + +func main() { + signalCliConfig := flag.String("signal-cli-config", "/home/.local/share/signal-cli/", "Config directory where signal-cli config is stored") + attachmentTmpDir := flag.String("attachment-tmp-dir", "/tmp/", "Attachment tmp directory") + flag.Parse() + + router := gin.Default() + log.Info("Starting...") + + router.POST("/v1/register/:number", func(c *gin.Context) { + number := c.Param("number") + + if number == "" { + c.JSON(400, "Please provide a number") + return + } + + err := runSignalCli([]string{"--config", *signalCliConfig, "-u", number, "register"}) + if err != nil { + c.JSON(400, err.Error()) + return + } + c.JSON(201, nil) + }) + + router.POST("/v1/register/:number/verify/:token", func(c *gin.Context) { + number := c.Param("number") + token := c.Param("token") + + if number == "" { + c.JSON(400, "Please provide a number") + return + } + + if token == "" { + c.JSON(400, gin.H{"error": "Please provide a verification code"}) + return + } + + + err := runSignalCli([]string{"--config", *signalCliConfig, "-u", number, "token", "verify", token}) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + c.JSON(201, nil) + }) + + router.POST("/v1/send", func(c *gin.Context) { + type Request struct{ + Number string `json:"number"` + Recipients []string `json:"recipients"` + Message string `json:"message"` + Base64Attachment string `json:"base64_attachment"` + } + var req Request + err := c.BindJSON(&req) + if err != nil { + c.JSON(400, "Couldn't process request - invalid request") + return + } + + cmd := []string{"--config", *signalCliConfig, "-u", req.Number, "send", "-m", req.Message} + cmd = append(cmd, req.Recipients...) + + attachmentTmpPath := "" + if(req.Base64Attachment != "") { + u, err := uuid.NewV4() + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + dec, err := base64.StdEncoding.DecodeString(req.Base64Attachment) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + fType, err := filetype.Get(dec) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + attachmentTmpPath := *attachmentTmpDir + u.String() + "." + fType.Extension + log.Info("attchment tmp path = ", attachmentTmpPath) + + f, err := os.Create(attachmentTmpPath) + if err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + defer f.Close() + + if _, err := f.Write(dec); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + if err := f.Sync(); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + + cmd = append(cmd, "-a") + cmd = append(cmd , attachmentTmpPath) + } + + err = runSignalCli(cmd) + if err != nil { + if attachmentTmpPath != "" { + os.Remove(attachmentTmpPath) + } + c.JSON(400, gin.H{"error": err.Error()}) + return + } + c.JSON(201, nil) + }) + + router.Run() +}