projects/sievemgr

changeset 0:b00673734e58

Initial revision

Split off sievemgr command from the managesieve repository.
author Guido Berhoerster <guido+sievemgr@berhoerster.name>
date Mon Oct 26 14:19:24 2020 +0100 (7 months ago)
parents
children 0cd5a454dfb4
files COPYING cmd/sievemgr/activate.go cmd/sievemgr/command.go cmd/sievemgr/common.go cmd/sievemgr/delete.go cmd/sievemgr/get.go cmd/sievemgr/info.go cmd/sievemgr/list.go cmd/sievemgr/main.go cmd/sievemgr/put.go go.mod go.sum
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/COPYING	Mon Oct 26 14:19:24 2020 +0100
     1.3 @@ -0,0 +1,20 @@
     1.4 +Copyright (C) 2020 Guido Berhoerster <guido+sievemgr@berhoerster.name>
     1.5 +
     1.6 +Permission is hereby granted, free of charge, to any person obtaining
     1.7 +a copy of this software and associated documentation files (the
     1.8 +"Software"), to deal in the Software without restriction, including
     1.9 +without limitation the rights to use, copy, modify, merge, publish,
    1.10 +distribute, sublicense, and/or sell copies of the Software, and to
    1.11 +permit persons to whom the Software is furnished to do so, subject to
    1.12 +the following conditions:
    1.13 +
    1.14 +The above copyright notice and this permission notice shall be included
    1.15 +in all copies or substantial portions of the Software.
    1.16 +
    1.17 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    1.18 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    1.19 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    1.20 +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    1.21 +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    1.22 +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    1.23 +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/cmd/sievemgr/activate.go	Mon Oct 26 14:19:24 2020 +0100
     2.3 @@ -0,0 +1,81 @@
     2.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     2.5 +//
     2.6 +// Permission is hereby granted, free of charge, to any person obtaining
     2.7 +// a copy of this software and associated documentation files (the
     2.8 +// "Software"), to deal in the Software without restriction, including
     2.9 +// without limitation the rights to use, copy, modify, merge, publish,
    2.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    2.11 +// permit persons to whom the Software is furnished to do so, subject to
    2.12 +// the following conditions:
    2.13 +//
    2.14 +// The above copyright notice and this permission notice shall be included
    2.15 +// in all copies or substantial portions of the Software.
    2.16 +//
    2.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    2.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    2.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    2.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    2.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    2.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    2.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    2.24 +
    2.25 +package main
    2.26 +
    2.27 +import (
    2.28 +	"net"
    2.29 +)
    2.30 +
    2.31 +func init() {
    2.32 +	cmdActivate.Flag.StringVar(&username, "u", "", "Set the username")
    2.33 +	cmdActivate.Flag.StringVar(&passwordFilename, "P", "",
    2.34 +		"Set the name of the password file")
    2.35 +	cmdDeactivate.Flag.StringVar(&username, "u", "", "Set the username")
    2.36 +	cmdDeactivate.Flag.StringVar(&passwordFilename, "P", "",
    2.37 +		"Set the name of the password file")
    2.38 +}
    2.39 +
    2.40 +var cmdActivate = &command{
    2.41 +	UsageLine: "activate [options] host[:port] name",
    2.42 +	Run:       runActivate,
    2.43 +}
    2.44 +
    2.45 +var cmdDeactivate = &command{
    2.46 +	UsageLine: "deactivate [options] host[:port]",
    2.47 +	Run:       runActivate,
    2.48 +}
    2.49 +
    2.50 +func runActivate(cmd *command, args []string) error {
    2.51 +	if (cmd.Name() == "activate" && len(args) != 2) ||
    2.52 +		(cmd.Name() == "deactivate" && len(args) != 1) {
    2.53 +		return usageError("invalid number of arguments")
    2.54 +	}
    2.55 +
    2.56 +	host, port, err := parseHostPort(args[0])
    2.57 +	if err != nil {
    2.58 +		return err
    2.59 +	}
    2.60 +
    2.61 +	var scriptName string
    2.62 +	if len(args) > 1 {
    2.63 +		scriptName = args[1]
    2.64 +	}
    2.65 +
    2.66 +	username, password, err := usernamePassword(host, port, username,
    2.67 +		passwordFilename)
    2.68 +	if err != nil {
    2.69 +		return err
    2.70 +	}
    2.71 +
    2.72 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
    2.73 +		password)
    2.74 +	if err != nil {
    2.75 +		return err
    2.76 +	}
    2.77 +	defer c.Logout()
    2.78 +
    2.79 +	if err := c.ActivateScript(scriptName); err != nil {
    2.80 +		return err
    2.81 +	}
    2.82 +
    2.83 +	return nil
    2.84 +}
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/cmd/sievemgr/command.go	Mon Oct 26 14:19:24 2020 +0100
     3.3 @@ -0,0 +1,45 @@
     3.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     3.5 +//
     3.6 +// Permission is hereby granted, free of charge, to any person obtaining
     3.7 +// a copy of this software and associated documentation files (the
     3.8 +// "Software"), to deal in the Software without restriction, including
     3.9 +// without limitation the rights to use, copy, modify, merge, publish,
    3.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    3.11 +// permit persons to whom the Software is furnished to do so, subject to
    3.12 +// the following conditions:
    3.13 +//
    3.14 +// The above copyright notice and this permission notice shall be included
    3.15 +// in all copies or substantial portions of the Software.
    3.16 +//
    3.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    3.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    3.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    3.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    3.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    3.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    3.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    3.24 +
    3.25 +package main
    3.26 +
    3.27 +import (
    3.28 +	"flag"
    3.29 +	"fmt"
    3.30 +	"strings"
    3.31 +)
    3.32 +
    3.33 +type command struct {
    3.34 +	UsageLine string
    3.35 +	Flag      flag.FlagSet
    3.36 +	Run       func(cmd *command, args []string) error
    3.37 +}
    3.38 +
    3.39 +func (c *command) Usage() {
    3.40 +	fmt.Fprintf(flag.CommandLine.Output(), "usage:\n  %s %s\n",
    3.41 +		flag.CommandLine.Name(), c.UsageLine)
    3.42 +	fmt.Fprintln(flag.CommandLine.Output(), "options:")
    3.43 +	c.Flag.PrintDefaults()
    3.44 +}
    3.45 +
    3.46 +func (c *command) Name() string {
    3.47 +	return strings.SplitN(strings.Trim(c.UsageLine, " "), " ", 2)[0]
    3.48 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/cmd/sievemgr/common.go	Mon Oct 26 14:19:24 2020 +0100
     4.3 @@ -0,0 +1,192 @@
     4.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     4.5 +//
     4.6 +// Permission is hereby granted, free of charge, to any person obtaining
     4.7 +// a copy of this software and associated documentation files (the
     4.8 +// "Software"), to deal in the Software without restriction, including
     4.9 +// without limitation the rights to use, copy, modify, merge, publish,
    4.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    4.11 +// permit persons to whom the Software is furnished to do so, subject to
    4.12 +// the following conditions:
    4.13 +//
    4.14 +// The above copyright notice and this permission notice shall be included
    4.15 +// in all copies or substantial portions of the Software.
    4.16 +//
    4.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    4.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    4.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    4.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    4.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    4.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    4.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    4.24 +
    4.25 +package main
    4.26 +
    4.27 +import (
    4.28 +	"bufio"
    4.29 +	"crypto/tls"
    4.30 +	"errors"
    4.31 +	"fmt"
    4.32 +	"io"
    4.33 +	"io/ioutil"
    4.34 +	"net"
    4.35 +	"os"
    4.36 +	"os/user"
    4.37 +	"runtime"
    4.38 +	"strings"
    4.39 +
    4.40 +	"go.guido-berhoerster.org/managesieve"
    4.41 +	"golang.org/x/crypto/ssh/terminal"
    4.42 +)
    4.43 +
    4.44 +var errTooBig = errors.New("too big")
    4.45 +
    4.46 +func parseHostPort(s string) (string, string, error) {
    4.47 +	var host string
    4.48 +	host, port, err := net.SplitHostPort(s)
    4.49 +	if err != nil {
    4.50 +		// error may be due to a missing port but there is no usable
    4.51 +		// error value to test for, thus try again with a port added
    4.52 +		var tmpErr error
    4.53 +		host, _, tmpErr = net.SplitHostPort(s + ":4190")
    4.54 +		if tmpErr != nil {
    4.55 +			return "", "", err
    4.56 +		}
    4.57 +	}
    4.58 +	if port == "" {
    4.59 +		// no port given, try to look up a SRV record for given domain
    4.60 +		// and fall back to the domain and port 4190
    4.61 +		services, err := managesieve.LookupService(host)
    4.62 +		if err != nil {
    4.63 +			return "", "", err
    4.64 +		}
    4.65 +		host, port, err = net.SplitHostPort(services[0])
    4.66 +		if err != nil {
    4.67 +			return "", "", err
    4.68 +		}
    4.69 +	}
    4.70 +	return host, port, nil
    4.71 +}
    4.72 +
    4.73 +func readPassword() (string, error) {
    4.74 +	var tty *os.File
    4.75 +	var fd int
    4.76 +	var w io.Writer
    4.77 +	if runtime.GOOS == "windows" {
    4.78 +		fd = int(os.Stdin.Fd())
    4.79 +		w = os.Stdout
    4.80 +	} else {
    4.81 +		var err error
    4.82 +		tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0666)
    4.83 +		if err != nil {
    4.84 +			return "", err
    4.85 +		}
    4.86 +		defer tty.Close()
    4.87 +		fd = int(tty.Fd())
    4.88 +		w = tty
    4.89 +	}
    4.90 +
    4.91 +	io.WriteString(w, "Password: ")
    4.92 +	rawPassword, err := terminal.ReadPassword(fd)
    4.93 +	io.WriteString(w, "\n")
    4.94 +	if err != nil {
    4.95 +		return "", fmt.Errorf("failed to read password: %s", err)
    4.96 +	}
    4.97 +	password := string(rawPassword)
    4.98 +	if password == "" {
    4.99 +		return "", fmt.Errorf("invalid password")
   4.100 +	}
   4.101 +	return password, nil
   4.102 +}
   4.103 +
   4.104 +func readPasswordFile(filename string) (string, error) {
   4.105 +	f, err := os.Open(filename)
   4.106 +	if err != nil {
   4.107 +		return "", err
   4.108 +	}
   4.109 +	defer f.Close()
   4.110 +	scanner := bufio.NewScanner(f)
   4.111 +	if !scanner.Scan() {
   4.112 +		err := scanner.Err()
   4.113 +		if err == nil {
   4.114 +			err = fmt.Errorf("failed to read from %q: unexpected EOF",
   4.115 +				filename)
   4.116 +		}
   4.117 +		return "", err
   4.118 +	}
   4.119 +	password := scanner.Text()
   4.120 +	if password == "" {
   4.121 +		return "", fmt.Errorf("invalid password")
   4.122 +	}
   4.123 +	return password, nil
   4.124 +}
   4.125 +
   4.126 +func usernamePassword(host, port, username, passwordFile string) (string, string, error) {
   4.127 +	// fall back to the system username
   4.128 +	if username == "" {
   4.129 +		u, err := user.Current()
   4.130 +		if err != nil {
   4.131 +			return "", "",
   4.132 +				fmt.Errorf("failed to obtain username: %s", err)
   4.133 +		}
   4.134 +		username = u.Username
   4.135 +	}
   4.136 +
   4.137 +	var password string
   4.138 +	var err error
   4.139 +	if passwordFile != "" {
   4.140 +		password, err = readPasswordFile(passwordFilename)
   4.141 +	} else {
   4.142 +		password, err = readPassword()
   4.143 +	}
   4.144 +	if err != nil {
   4.145 +		return "", "", err
   4.146 +	}
   4.147 +
   4.148 +	return username, password, nil
   4.149 +}
   4.150 +
   4.151 +func dialPlainAuth(hostport, username, password string) (*managesieve.Client, error) {
   4.152 +	c, err := managesieve.Dial(hostport)
   4.153 +	if err != nil {
   4.154 +		return nil, fmt.Errorf("failed to connect: %s", err)
   4.155 +	}
   4.156 +
   4.157 +	host, _, _ := net.SplitHostPort(hostport)
   4.158 +	// switch to a TLS connection except for localhost
   4.159 +	if host != "localhost" && host != "127.0.0.1" && host != "::1" {
   4.160 +		tlsConf := &tls.Config{
   4.161 +			ServerName:         host,
   4.162 +			InsecureSkipVerify: skipCertVerify,
   4.163 +		}
   4.164 +		if err := c.StartTLS(tlsConf); err != nil {
   4.165 +			return nil,
   4.166 +				fmt.Errorf("failed to start TLS connection: %s",
   4.167 +					err)
   4.168 +		}
   4.169 +	}
   4.170 +
   4.171 +	auth := managesieve.PlainAuth("", username, password, host)
   4.172 +	if err := c.Authenticate(auth); err != nil {
   4.173 +		return nil, fmt.Errorf("failed to authenticate user %s: %s",
   4.174 +			username, err)
   4.175 +	}
   4.176 +
   4.177 +	return c, nil
   4.178 +}
   4.179 +
   4.180 +func readLimitedString(r io.Reader, n int64) (string, error) {
   4.181 +	var s strings.Builder
   4.182 +	_, err := io.CopyN(&s, r, n)
   4.183 +	if err == nil {
   4.184 +		// check for EOF
   4.185 +		_, err = io.CopyN(ioutil.Discard, r, 1)
   4.186 +		if err == nil {
   4.187 +			return s.String(), errTooBig
   4.188 +		}
   4.189 +	}
   4.190 +	if err != io.EOF {
   4.191 +		return s.String(), err
   4.192 +	}
   4.193 +
   4.194 +	return s.String(), nil
   4.195 +}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/cmd/sievemgr/delete.go	Mon Oct 26 14:19:24 2020 +0100
     5.3 @@ -0,0 +1,69 @@
     5.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     5.5 +//
     5.6 +// Permission is hereby granted, free of charge, to any person obtaining
     5.7 +// a copy of this software and associated documentation files (the
     5.8 +// "Software"), to deal in the Software without restriction, including
     5.9 +// without limitation the rights to use, copy, modify, merge, publish,
    5.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    5.11 +// permit persons to whom the Software is furnished to do so, subject to
    5.12 +// the following conditions:
    5.13 +//
    5.14 +// The above copyright notice and this permission notice shall be included
    5.15 +// in all copies or substantial portions of the Software.
    5.16 +//
    5.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    5.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    5.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    5.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    5.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    5.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    5.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    5.24 +
    5.25 +package main
    5.26 +
    5.27 +import (
    5.28 +	"net"
    5.29 +)
    5.30 +
    5.31 +func init() {
    5.32 +	cmdDelete.Flag.StringVar(&username, "u", "", "Set the username")
    5.33 +	cmdDelete.Flag.StringVar(&passwordFilename, "P", "",
    5.34 +		"Set the name of the password file")
    5.35 +}
    5.36 +
    5.37 +var cmdDelete = &command{
    5.38 +	UsageLine: "delete [options] host[:port] name",
    5.39 +	Run:       runDelete,
    5.40 +}
    5.41 +
    5.42 +func runDelete(cmd *command, args []string) error {
    5.43 +	if len(args) != 2 {
    5.44 +		return usageError("invalid number of arguments")
    5.45 +	}
    5.46 +
    5.47 +	host, port, err := parseHostPort(args[0])
    5.48 +	if err != nil {
    5.49 +		return err
    5.50 +	}
    5.51 +
    5.52 +	scriptName := args[1]
    5.53 +
    5.54 +	username, password, err := usernamePassword(host, port, username,
    5.55 +		passwordFilename)
    5.56 +	if err != nil {
    5.57 +		return err
    5.58 +	}
    5.59 +
    5.60 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
    5.61 +		password)
    5.62 +	if err != nil {
    5.63 +		return err
    5.64 +	}
    5.65 +	defer c.Logout()
    5.66 +
    5.67 +	if err := c.DeleteScript(scriptName); err != nil {
    5.68 +		return err
    5.69 +	}
    5.70 +
    5.71 +	return nil
    5.72 +}
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/cmd/sievemgr/get.go	Mon Oct 26 14:19:24 2020 +0100
     6.3 @@ -0,0 +1,72 @@
     6.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     6.5 +//
     6.6 +// Permission is hereby granted, free of charge, to any person obtaining
     6.7 +// a copy of this software and associated documentation files (the
     6.8 +// "Software"), to deal in the Software without restriction, including
     6.9 +// without limitation the rights to use, copy, modify, merge, publish,
    6.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    6.11 +// permit persons to whom the Software is furnished to do so, subject to
    6.12 +// the following conditions:
    6.13 +//
    6.14 +// The above copyright notice and this permission notice shall be included
    6.15 +// in all copies or substantial portions of the Software.
    6.16 +//
    6.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    6.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    6.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    6.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    6.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    6.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    6.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    6.24 +
    6.25 +package main
    6.26 +
    6.27 +import (
    6.28 +	"net"
    6.29 +	"os"
    6.30 +)
    6.31 +
    6.32 +func init() {
    6.33 +	cmdGet.Flag.StringVar(&username, "u", "", "Set the username")
    6.34 +	cmdGet.Flag.StringVar(&passwordFilename, "P", "",
    6.35 +		"Set the name of the password file")
    6.36 +}
    6.37 +
    6.38 +var cmdGet = &command{
    6.39 +	UsageLine: "get [options] host[:port] name",
    6.40 +	Run:       runGet,
    6.41 +}
    6.42 +
    6.43 +func runGet(cmd *command, args []string) error {
    6.44 +	if len(args) != 2 {
    6.45 +		return usageError("invalid number of arguments")
    6.46 +	}
    6.47 +
    6.48 +	host, port, err := parseHostPort(args[0])
    6.49 +	if err != nil {
    6.50 +		return err
    6.51 +	}
    6.52 +
    6.53 +	scriptName := args[1]
    6.54 +
    6.55 +	username, password, err := usernamePassword(host, port, username,
    6.56 +		passwordFilename)
    6.57 +	if err != nil {
    6.58 +		return err
    6.59 +	}
    6.60 +
    6.61 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
    6.62 +		password)
    6.63 +	if err != nil {
    6.64 +		return err
    6.65 +	}
    6.66 +	defer c.Logout()
    6.67 +
    6.68 +	script, err := c.GetScript(scriptName)
    6.69 +	if err != nil {
    6.70 +		return err
    6.71 +	}
    6.72 +	os.Stdout.WriteString(script)
    6.73 +
    6.74 +	return nil
    6.75 +}
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/cmd/sievemgr/info.go	Mon Oct 26 14:19:24 2020 +0100
     7.3 @@ -0,0 +1,84 @@
     7.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     7.5 +//
     7.6 +// Permission is hereby granted, free of charge, to any person obtaining
     7.7 +// a copy of this software and associated documentation files (the
     7.8 +// "Software"), to deal in the Software without restriction, including
     7.9 +// without limitation the rights to use, copy, modify, merge, publish,
    7.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    7.11 +// permit persons to whom the Software is furnished to do so, subject to
    7.12 +// the following conditions:
    7.13 +//
    7.14 +// The above copyright notice and this permission notice shall be included
    7.15 +// in all copies or substantial portions of the Software.
    7.16 +//
    7.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    7.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    7.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    7.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    7.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    7.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    7.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    7.24 +
    7.25 +package main
    7.26 +
    7.27 +import (
    7.28 +	"fmt"
    7.29 +	"net"
    7.30 +)
    7.31 +
    7.32 +func init() {
    7.33 +	cmdInfo.Flag.StringVar(&username, "u", "", "Set the username")
    7.34 +	cmdInfo.Flag.StringVar(&passwordFilename, "P", "",
    7.35 +		"Set the name of the password file")
    7.36 +}
    7.37 +
    7.38 +var cmdInfo = &command{
    7.39 +	UsageLine: "info [options] host[:port]",
    7.40 +	Run:       runInfo,
    7.41 +}
    7.42 +
    7.43 +func runInfo(cmd *command, args []string) error {
    7.44 +	if len(args) != 1 {
    7.45 +		return usageError("invalid number of arguments")
    7.46 +	}
    7.47 +
    7.48 +	host, port, err := parseHostPort(args[0])
    7.49 +	if err != nil {
    7.50 +		return err
    7.51 +	}
    7.52 +
    7.53 +	username, password, err := usernamePassword(host, port, username,
    7.54 +		passwordFilename)
    7.55 +	if err != nil {
    7.56 +		return err
    7.57 +	}
    7.58 +
    7.59 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
    7.60 +		password)
    7.61 +	if err != nil {
    7.62 +		return err
    7.63 +	}
    7.64 +	defer c.Logout()
    7.65 +
    7.66 +	fmt.Println(net.JoinHostPort(host, port))
    7.67 +	if c.SupportsRFC5804() {
    7.68 +		fmt.Println("RFC5804 supported")
    7.69 +	}
    7.70 +	if c.SupportsTLS() {
    7.71 +		fmt.Println("STARTTLS supported")
    7.72 +	}
    7.73 +	fmt.Printf("SASL mechanisms:\n")
    7.74 +	for _, m := range c.SASLMechanisms() {
    7.75 +		fmt.Printf("  %s\n", m)
    7.76 +	}
    7.77 +	fmt.Printf("Extensions:\n")
    7.78 +	for _, e := range c.Extensions() {
    7.79 +		fmt.Printf("  %s\n", e)
    7.80 +	}
    7.81 +	fmt.Printf("Notification methods:\n")
    7.82 +	for _, m := range c.NotifyMethods() {
    7.83 +		fmt.Printf("  %s\n", m)
    7.84 +	}
    7.85 +
    7.86 +	return nil
    7.87 +}
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/cmd/sievemgr/list.go	Mon Oct 26 14:19:24 2020 +0100
     8.3 @@ -0,0 +1,77 @@
     8.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     8.5 +//
     8.6 +// Permission is hereby granted, free of charge, to any person obtaining
     8.7 +// a copy of this software and associated documentation files (the
     8.8 +// "Software"), to deal in the Software without restriction, including
     8.9 +// without limitation the rights to use, copy, modify, merge, publish,
    8.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    8.11 +// permit persons to whom the Software is furnished to do so, subject to
    8.12 +// the following conditions:
    8.13 +//
    8.14 +// The above copyright notice and this permission notice shall be included
    8.15 +// in all copies or substantial portions of the Software.
    8.16 +//
    8.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    8.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    8.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    8.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    8.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    8.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    8.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    8.24 +
    8.25 +package main
    8.26 +
    8.27 +import (
    8.28 +	"fmt"
    8.29 +	"net"
    8.30 +)
    8.31 +
    8.32 +func init() {
    8.33 +	cmdList.Flag.StringVar(&username, "u", "", "Set the username")
    8.34 +	cmdList.Flag.StringVar(&passwordFilename, "P", "",
    8.35 +		"Set the name of the password file")
    8.36 +}
    8.37 +
    8.38 +var cmdList = &command{
    8.39 +	UsageLine: "list [options] host[:port]",
    8.40 +	Run:       runList,
    8.41 +}
    8.42 +
    8.43 +func runList(cmd *command, args []string) error {
    8.44 +	if len(args) != 1 {
    8.45 +		return usageError("invalid number of arguments")
    8.46 +	}
    8.47 +
    8.48 +	host, port, err := parseHostPort(args[0])
    8.49 +	if err != nil {
    8.50 +		return err
    8.51 +	}
    8.52 +
    8.53 +	username, password, err := usernamePassword(host, port, username,
    8.54 +		passwordFilename)
    8.55 +	if err != nil {
    8.56 +		return err
    8.57 +	}
    8.58 +
    8.59 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
    8.60 +		password)
    8.61 +	if err != nil {
    8.62 +		return err
    8.63 +	}
    8.64 +	defer c.Logout()
    8.65 +
    8.66 +	scripts, active, _ := c.ListScripts()
    8.67 +	if err != nil {
    8.68 +		return err
    8.69 +	}
    8.70 +	fmt.Println("ACTIVE  SCRIPT")
    8.71 +	for _, script := range scripts {
    8.72 +		isActive := "no"
    8.73 +		if script == active {
    8.74 +			isActive = "yes"
    8.75 +		}
    8.76 +		fmt.Printf("%-3s     %s\n", isActive, script)
    8.77 +	}
    8.78 +
    8.79 +	return nil
    8.80 +}
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/cmd/sievemgr/main.go	Mon Oct 26 14:19:24 2020 +0100
     9.3 @@ -0,0 +1,116 @@
     9.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
     9.5 +//
     9.6 +// Permission is hereby granted, free of charge, to any person obtaining
     9.7 +// a copy of this software and associated documentation files (the
     9.8 +// "Software"), to deal in the Software without restriction, including
     9.9 +// without limitation the rights to use, copy, modify, merge, publish,
    9.10 +// distribute, sublicense, and/or sell copies of the Software, and to
    9.11 +// permit persons to whom the Software is furnished to do so, subject to
    9.12 +// the following conditions:
    9.13 +//
    9.14 +// The above copyright notice and this permission notice shall be included
    9.15 +// in all copies or substantial portions of the Software.
    9.16 +//
    9.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    9.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    9.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    9.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    9.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    9.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    9.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    9.24 +
    9.25 +package main
    9.26 +
    9.27 +import (
    9.28 +	"errors"
    9.29 +	"flag"
    9.30 +	"fmt"
    9.31 +	"os"
    9.32 +)
    9.33 +
    9.34 +const (
    9.35 +	exitSuccess = iota
    9.36 +	exitFailure
    9.37 +	exitUsage
    9.38 +)
    9.39 +
    9.40 +type usageError string
    9.41 +
    9.42 +func (e usageError) Error() string {
    9.43 +	return string(e)
    9.44 +}
    9.45 +
    9.46 +var (
    9.47 +	skipCertVerify   bool
    9.48 +	username         string
    9.49 +	passwordFilename string
    9.50 +)
    9.51 +
    9.52 +var cmds = []*command{
    9.53 +	cmdList,
    9.54 +	cmdPut,
    9.55 +	cmdGet,
    9.56 +	cmdActivate,
    9.57 +	cmdDeactivate,
    9.58 +	cmdDelete,
    9.59 +	cmdInfo,
    9.60 +}
    9.61 +
    9.62 +func usage() {
    9.63 +	fmt.Fprintf(flag.CommandLine.Output(),
    9.64 +		"usage:\n  %s [options] [subcommand [options] [arguments]]\n",
    9.65 +		flag.CommandLine.Name())
    9.66 +	fmt.Fprintln(flag.CommandLine.Output(), "subcommands:")
    9.67 +	for _, cmd := range cmds {
    9.68 +		fmt.Fprintf(flag.CommandLine.Output(), "  %s\n", cmd.Name())
    9.69 +	}
    9.70 +	fmt.Fprintln(flag.CommandLine.Output(), "global options:")
    9.71 +	flag.PrintDefaults()
    9.72 +}
    9.73 +
    9.74 +func main() {
    9.75 +	flag.Usage = usage
    9.76 +	flag.BoolVar(&skipCertVerify, "I", false,
    9.77 +		"Skip TLS certificate verification")
    9.78 +	flag.Parse()
    9.79 +	if flag.NArg() == 0 {
    9.80 +		fmt.Fprintln(flag.CommandLine.Output(), "missing subcommand")
    9.81 +		usage()
    9.82 +		os.Exit(exitUsage)
    9.83 +	}
    9.84 +
    9.85 +	name := flag.Arg(0)
    9.86 +	var cmd *command
    9.87 +	for _, c := range cmds {
    9.88 +		if c.Name() == name {
    9.89 +			cmd = c
    9.90 +			break
    9.91 +		}
    9.92 +	}
    9.93 +	if cmd == nil {
    9.94 +		fmt.Fprintf(flag.CommandLine.Output(),
    9.95 +			"unknown subcommand %q\n", name)
    9.96 +		usage()
    9.97 +		os.Exit(exitUsage)
    9.98 +	}
    9.99 +
   9.100 +	cmd.Flag.Init(cmd.Name(), flag.ExitOnError)
   9.101 +	cmd.Flag.Usage = cmd.Usage
   9.102 +	args := flag.Args()
   9.103 +	if err := cmd.Flag.Parse(args[1:]); err != nil {
   9.104 +		fmt.Fprintln(flag.CommandLine.Output(), err)
   9.105 +		os.Exit(exitFailure)
   9.106 +	}
   9.107 +
   9.108 +	if err := cmd.Run(cmd, cmd.Flag.Args()); err != nil {
   9.109 +		fmt.Fprintln(flag.CommandLine.Output(), err)
   9.110 +
   9.111 +		var uerr usageError
   9.112 +		if errors.As(err, &uerr) {
   9.113 +			cmd.Usage()
   9.114 +			os.Exit(exitUsage)
   9.115 +		}
   9.116 +
   9.117 +		os.Exit(exitFailure)
   9.118 +	}
   9.119 +}
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/cmd/sievemgr/put.go	Mon Oct 26 14:19:24 2020 +0100
    10.3 @@ -0,0 +1,93 @@
    10.4 +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
    10.5 +//
    10.6 +// Permission is hereby granted, free of charge, to any person obtaining
    10.7 +// a copy of this software and associated documentation files (the
    10.8 +// "Software"), to deal in the Software without restriction, including
    10.9 +// without limitation the rights to use, copy, modify, merge, publish,
   10.10 +// distribute, sublicense, and/or sell copies of the Software, and to
   10.11 +// permit persons to whom the Software is furnished to do so, subject to
   10.12 +// the following conditions:
   10.13 +//
   10.14 +// The above copyright notice and this permission notice shall be included
   10.15 +// in all copies or substantial portions of the Software.
   10.16 +//
   10.17 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   10.18 +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   10.19 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   10.20 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   10.21 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   10.22 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   10.23 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   10.24 +
   10.25 +package main
   10.26 +
   10.27 +import (
   10.28 +	"fmt"
   10.29 +	"io"
   10.30 +	"net"
   10.31 +	"os"
   10.32 +
   10.33 +	"go.guido-berhoerster.org/managesieve"
   10.34 +)
   10.35 +
   10.36 +func init() {
   10.37 +	cmdPut.Flag.StringVar(&username, "u", "", "Set the username")
   10.38 +	cmdPut.Flag.StringVar(&passwordFilename, "P", "",
   10.39 +		"Set the name of the password file")
   10.40 +}
   10.41 +
   10.42 +var cmdPut = &command{
   10.43 +	UsageLine: "put [options] host[:port] name [file]",
   10.44 +	Run:       runPut,
   10.45 +}
   10.46 +
   10.47 +func runPut(cmd *command, args []string) error {
   10.48 +	var scriptName string
   10.49 +	var r io.Reader = os.Stdin
   10.50 +	var host, port string
   10.51 +	var err error
   10.52 +	switch len(args) {
   10.53 +	case 3: // name and filename
   10.54 +		scriptFile, err := os.Open(args[2])
   10.55 +		if err != nil {
   10.56 +			return fmt.Errorf("failed to open script file: %s\n",
   10.57 +				err)
   10.58 +		}
   10.59 +		defer scriptFile.Close()
   10.60 +		r = scriptFile
   10.61 +		fallthrough
   10.62 +	case 2: // only name
   10.63 +		host, port, err = parseHostPort(args[0])
   10.64 +		if err != nil {
   10.65 +			return err
   10.66 +		}
   10.67 +
   10.68 +		scriptName = args[1]
   10.69 +	default:
   10.70 +		return usageError("invalid number of arguments")
   10.71 +	}
   10.72 +
   10.73 +	script, err := readLimitedString(r, managesieve.ReadLimit)
   10.74 +	if err != nil {
   10.75 +		return fmt.Errorf("failed to read script: %s\n", err)
   10.76 +	}
   10.77 +
   10.78 +	username, password, err := usernamePassword(host, port, username,
   10.79 +		passwordFilename)
   10.80 +	if err != nil {
   10.81 +		return err
   10.82 +	}
   10.83 +
   10.84 +	c, err := dialPlainAuth(net.JoinHostPort(host, port), username,
   10.85 +		password)
   10.86 +	if err != nil {
   10.87 +		return err
   10.88 +	}
   10.89 +	defer c.Logout()
   10.90 +
   10.91 +	if err := c.PutScript(scriptName, script); err != nil {
   10.92 +		return err
   10.93 +	}
   10.94 +
   10.95 +	return nil
   10.96 +}
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/go.mod	Mon Oct 26 14:19:24 2020 +0100
    11.3 @@ -0,0 +1,8 @@
    11.4 +module go.guido-berhoerster.org/sievemgr
    11.5 +
    11.6 +go 1.14
    11.7 +
    11.8 +require (
    11.9 +	go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c
   11.10 +	golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
   11.11 +)
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/go.sum	Mon Oct 26 14:19:24 2020 +0100
    12.3 @@ -0,0 +1,12 @@
    12.4 +go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c h1:4HErjjy83de5F6xWn9Fd+N31KDJh0h1jibO6n0Ye6lw=
    12.5 +go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c/go.mod h1:uvqvWGFO2zONQiEQuDNH37Mg2pM7gTsDZSXtBOSmj98=
    12.6 +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
    12.7 +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
    12.8 +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
    12.9 +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
   12.10 +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
   12.11 +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
   12.12 +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
   12.13 +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
   12.14 +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
   12.15 +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=