git » chasquid » commit 5c09138

chasquid-util: Remove dependency on docopt-go

author Alberto Bertogli
2021-01-16 14:19:50 UTC
committer Alberto Bertogli
2021-01-16 23:21:35 UTC
parent def6e1cee2b78f4bd8e2ac8591532e5c59ccd58d

chasquid-util: Remove dependency on docopt-go

The docopt-go library is quite convenient, but it has been abandoned for
a while :(

Since we only use it for chasquid-util, this patch removes it and
replaces it with a custom small parser, that is a reasonable fit for the
required use cases.

The patch also adds a couple of tests to increase coverage.

NOTE: docopt-go accepted some undocumented behaviour, in particular the
use of "-a b" instead of "-a=b". The new parser does not, so some
user scripts may require updating.

I think this should be rare enough not to be worth the complexity of
adjusting the parser to allow it.

cmd/chasquid-util/chasquid-util.go +70 -19
cmd/chasquid-util/test.sh +22 -1
docs/relnotes.md +8 -0
go.mod +0 -1
go.sum +0 -2
test/util/lib.sh +2 -2

diff --git a/cmd/chasquid-util/chasquid-util.go b/cmd/chasquid-util/chasquid-util.go
index 5b4aef7..57a12db 100644
--- a/cmd/chasquid-util/chasquid-util.go
+++ b/cmd/chasquid-util/chasquid-util.go
@@ -6,15 +6,16 @@
 package main
 
 import (
+	"bytes"
 	"fmt"
 	"io/ioutil"
 	"net/url"
 	"os"
 	"path/filepath"
+	"strconv"
+	"strings"
 	"syscall"
 
-	"bytes"
-
 	"blitiri.com.ar/go/chasquid/internal/aliases"
 	"blitiri.com.ar/go/chasquid/internal/config"
 	"blitiri.com.ar/go/chasquid/internal/envelope"
@@ -22,11 +23,12 @@ import (
 	"blitiri.com.ar/go/chasquid/internal/userdb"
 	"google.golang.org/protobuf/encoding/prototext"
 
-	"github.com/docopt/docopt-go"
+	// TODO: Move to golang.org/x/term once we don't support Go 1.11 anymore,
+	// since this one is deprecated (but still fully functional, so no rush).
 	"golang.org/x/crypto/ssh/terminal"
 )
 
-// Usage, which doubles as parameter definitions thanks to docopt.
+// Usage to show users on --help or invocation errors.
 const usage = `
 Usage:
   chasquid-util [options] user-add <user@domain> [--password=<password>]
@@ -39,11 +41,16 @@ Usage:
   chasquid-util [options] aliases-add <source> <target>
 
 Options:
-  -C --configdir=<path>  Configuration directory
+  -C=<path>, --configdir=<path>  Configuration directory
 `
 
 // Command-line arguments.
-var args map[string]interface{}
+// Arguments starting with "-" will be parsed as key-value pairs, and
+// positional arguments will appear as "$POS" -> value.
+//
+// For example, "--abc=def x y -p=q -r" will result in:
+// {"--abc": "def", "$1": "x", "$2": "y", "-p": "q", "-r": ""}
+var args map[string]string
 
 // Globals, loaded from top-level options.
 var (
@@ -51,10 +58,18 @@ var (
 )
 
 func main() {
-	args, _ = docopt.ParseDoc(usage)
+	args = parseArgs(usage)
+
+	if _, ok := args["--help"]; ok {
+		fmt.Print(usage)
+		return
+	}
 
 	// Load globals.
-	if d, ok := args["--configdir"].(string); ok {
+	if d, ok := args["--configdir"]; ok {
+		configDir = d
+	}
+	if d, ok := args["-C"]; ok {
 		configDir = d
 	}
 
@@ -69,10 +84,12 @@ func main() {
 		"aliases-add":       aliasesAdd,
 	}
 
-	for cmd, f := range commands {
-		if args[cmd].(bool) {
-			f()
-		}
+	cmd := args["$1"]
+	if f, ok := commands[cmd]; ok {
+		f()
+	} else {
+		fmt.Printf("Unknown argument %q\n", cmd)
+		Fatalf(usage)
 	}
 }
 
@@ -84,13 +101,13 @@ func Fatalf(s string, arg ...interface{}) {
 
 func userDBForDomain(domain string) string {
 	if domain == "" {
-		domain = args["<domain>"].(string)
+		domain = args["$2"]
 	}
 	return configDir + "/domains/" + domain + "/users"
 }
 
 func userDBFromArgs(create bool) (string, string, *userdb.DB) {
-	username := args["<user@domain>"].(string)
+	username := args["$2"]
 	user, domain := envelope.Split(username)
 	if domain == "" {
 		Fatalf("Domain missing, username should be of the form 'user@domain'")
@@ -159,7 +176,7 @@ func authenticate() {
 }
 
 func getPassword() string {
-	password, ok := args["--password"].(string)
+	password, ok := args["--password"]
 	if ok {
 		return password
 	}
@@ -236,7 +253,7 @@ func aliasesResolve() {
 		}
 	}
 
-	rcpts, err := r.Resolve(args["<address>"].(string))
+	rcpts, err := r.Resolve(args["$2"])
 	if err != nil {
 		Fatalf("Error resolving: %v", err)
 	}
@@ -258,7 +275,7 @@ func printConfig() {
 
 // chasquid-util domaininfo-remove <domain>
 func domaininfoRemove() {
-	domain := args["<domain>"].(string)
+	domain := args["$2"]
 
 	conf, err := config.Load(configDir+"/chasquid.conf", "")
 	if err != nil {
@@ -277,14 +294,18 @@ func domaininfoRemove() {
 
 // chasquid-util aliases-add <source> <target>
 func aliasesAdd() {
-	source := args["<source>"].(string)
-	target := args["<target>"].(string)
+	source := args["$2"]
+	target := args["$3"]
 
 	user, domain := envelope.Split(source)
 	if domain == "" {
 		Fatalf("Domain required in source address")
 	}
 
+	if target == "" {
+		Fatalf("Target must be present")
+	}
+
 	// Ensure the domain exists.
 	if _, err := os.Stat(filepath.Join(configDir, "domains", domain)); os.IsNotExist(err) {
 		Fatalf("Domain doesn't exist")
@@ -324,3 +345,33 @@ func aliasesAdd() {
 	aliasesFile.Close()
 	fmt.Println("Added alias")
 }
+
+// parseArgs parses the command line arguments, and returns a map.
+//
+// Arguments starting with "-" will be parsed as key-value pairs, and
+// positional arguments will appear as "$POS" -> value.
+//
+// For example, "--abc=def x y -p=q -r" will result in:
+// {"--abc": "def", "$1": "x", "$2": "y", "-p": "q", "-r": ""}
+func parseArgs(usage string) map[string]string {
+	args := map[string]string{}
+
+	pos := 1
+	for _, a := range os.Args[1:] {
+		// Note: Consider handling end of args marker "--" explicitly in
+		// the future if needed.
+		if strings.HasPrefix(a, "-") {
+			sp := strings.SplitN(a, "=", 2)
+			if len(sp) < 2 {
+				args[a] = ""
+			} else {
+				args[sp[0]] = sp[1]
+			}
+		} else {
+			args["$"+strconv.Itoa(pos)] = a
+			pos++
+		}
+	}
+
+	return args
+}
diff --git a/cmd/chasquid-util/test.sh b/cmd/chasquid-util/test.sh
index c549b51..8b884d7 100755
--- a/cmd/chasquid-util/test.sh
+++ b/cmd/chasquid-util/test.sh
@@ -8,7 +8,7 @@ init
 go build || exit 1
 
 function r() {
-	./chasquid-util -C .config "$@"
+	./chasquid-util -C=.config "$@"
 }
 
 function check_userdb() {
@@ -44,6 +44,22 @@ if r authenticate user@domain --password=abcd > /dev/null; then
 	exit 1
 fi
 
+# Interactive authentication.
+# Need to wrap the execution under "script" since the interaction requires an
+# actual TTY, and that's a fairly portable way to do that.
+if hash script 2>/dev/null; then
+	if ! (echo passwd; echo passwd ) \
+		| script \
+			-qfec "./chasquid-util -C=.config authenticate user@domain" \
+			".script-out" \
+		| grep -q "Authentication succeeded";
+	then
+		echo interactive authenticate failed
+		exit 1
+	fi
+fi
+
+
 if ! r user-remove user@domain > /dev/null; then
 	echo user-remove failed
 	exit 1
@@ -99,4 +115,9 @@ if r aliases-add alias3@notexist target > /dev/null; then
 	exit 1
 fi
 
+if r aliases-add alias4@domain > /dev/null; then
+	echo aliases-add without target worked
+	exit 1
+fi
+
 success
diff --git a/docs/relnotes.md b/docs/relnotes.md
index 61de06c..c7c96d5 100644
--- a/docs/relnotes.md
+++ b/docs/relnotes.md
@@ -5,6 +5,14 @@ This file contains notes for each release, summarizing changes and explicitly
 noting backward-incompatible changes or known security issues.
 
 
+## 1.7 (TODO)
+
+- chasquid-util no longer depends on the unmaintained docopt-go.
+  If you relied on undocumented parsing behaviour before, your invocations may
+  need adjustment.  In particular, `--a b` is no longer supported, and `--a=b`
+  must be used instead.
+
+
 ## 1.6 (2020-11-22)
 
 - Pass the EHLO domain to the post-data hook.
diff --git a/go.mod b/go.mod
index f08fe89..98f2141 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ require (
 	blitiri.com.ar/go/log v1.1.0
 	blitiri.com.ar/go/spf v1.1.1
 	blitiri.com.ar/go/systemd v1.1.0
-	github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815
 	github.com/golang/protobuf v1.4.2
 	github.com/google/go-cmp v0.4.0
 	golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
diff --git a/go.sum b/go.sum
index d03b337..49d1a3a 100644
--- a/go.sum
+++ b/go.sum
@@ -4,8 +4,6 @@ blitiri.com.ar/go/spf v1.1.1 h1:H5MKnEe5feN4NjtPDK/vFkkS0fI+ecTIsOfLNCR+6yI=
 blitiri.com.ar/go/spf v1.1.1/go.mod h1:HLmgHxdrsqbBgi5omEopdAKm18PypvUKJGkF/j7BO0w=
 blitiri.com.ar/go/systemd v1.1.0 h1:AMr7Ce/5CkvLZvGxsn/ZOagzFf3zU13rcgWdlbWMQ+Y=
 blitiri.com.ar/go/systemd v1.1.0/go.mod h1:0D9Ttrh+TX+WuKQ/dJpdhFND7NYy505v6jhsWrihmPY=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
-github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
 github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
 github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
diff --git a/test/util/lib.sh b/test/util/lib.sh
index b0bf3fe..b9d343c 100644
--- a/test/util/lib.sh
+++ b/test/util/lib.sh
@@ -65,9 +65,9 @@ function add_user() {
 	DOMAIN=$(echo $1 | cut -d @ -f 2)
 	mkdir -p "${CONFDIR}/domains/$DOMAIN/"
 	go run ${TBASE}/../../cmd/chasquid-util/chasquid-util.go \
-		-C "${CONFDIR}" \
+		-C="${CONFDIR}" \
 		user-add "$1" \
-		--password "$2" \
+		--password="$2" \
 		>> .add_user_logs
 }