# HG changeset patch # User Guido Berhoerster # Date 1603718364 -3600 # Node ID b00673734e582e27436c301a77e600065ad1d9db Initial revision Split off sievemgr command from the managesieve repository. diff -r 000000000000 -r b00673734e58 COPYING --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/COPYING Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,20 @@ +Copyright (C) 2020 Guido Berhoerster + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff -r 000000000000 -r b00673734e58 cmd/sievemgr/activate.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/activate.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,81 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "net" +) + +func init() { + cmdActivate.Flag.StringVar(&username, "u", "", "Set the username") + cmdActivate.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") + cmdDeactivate.Flag.StringVar(&username, "u", "", "Set the username") + cmdDeactivate.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdActivate = &command{ + UsageLine: "activate [options] host[:port] name", + Run: runActivate, +} + +var cmdDeactivate = &command{ + UsageLine: "deactivate [options] host[:port]", + Run: runActivate, +} + +func runActivate(cmd *command, args []string) error { + if (cmd.Name() == "activate" && len(args) != 2) || + (cmd.Name() == "deactivate" && len(args) != 1) { + return usageError("invalid number of arguments") + } + + host, port, err := parseHostPort(args[0]) + if err != nil { + return err + } + + var scriptName string + if len(args) > 1 { + scriptName = args[1] + } + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + if err := c.ActivateScript(scriptName); err != nil { + return err + } + + return nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/command.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/command.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,45 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "flag" + "fmt" + "strings" +) + +type command struct { + UsageLine string + Flag flag.FlagSet + Run func(cmd *command, args []string) error +} + +func (c *command) Usage() { + fmt.Fprintf(flag.CommandLine.Output(), "usage:\n %s %s\n", + flag.CommandLine.Name(), c.UsageLine) + fmt.Fprintln(flag.CommandLine.Output(), "options:") + c.Flag.PrintDefaults() +} + +func (c *command) Name() string { + return strings.SplitN(strings.Trim(c.UsageLine, " "), " ", 2)[0] +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/common.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/common.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,192 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "bufio" + "crypto/tls" + "errors" + "fmt" + "io" + "io/ioutil" + "net" + "os" + "os/user" + "runtime" + "strings" + + "go.guido-berhoerster.org/managesieve" + "golang.org/x/crypto/ssh/terminal" +) + +var errTooBig = errors.New("too big") + +func parseHostPort(s string) (string, string, error) { + var host string + host, port, err := net.SplitHostPort(s) + if err != nil { + // error may be due to a missing port but there is no usable + // error value to test for, thus try again with a port added + var tmpErr error + host, _, tmpErr = net.SplitHostPort(s + ":4190") + if tmpErr != nil { + return "", "", err + } + } + if port == "" { + // no port given, try to look up a SRV record for given domain + // and fall back to the domain and port 4190 + services, err := managesieve.LookupService(host) + if err != nil { + return "", "", err + } + host, port, err = net.SplitHostPort(services[0]) + if err != nil { + return "", "", err + } + } + return host, port, nil +} + +func readPassword() (string, error) { + var tty *os.File + var fd int + var w io.Writer + if runtime.GOOS == "windows" { + fd = int(os.Stdin.Fd()) + w = os.Stdout + } else { + var err error + tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0666) + if err != nil { + return "", err + } + defer tty.Close() + fd = int(tty.Fd()) + w = tty + } + + io.WriteString(w, "Password: ") + rawPassword, err := terminal.ReadPassword(fd) + io.WriteString(w, "\n") + if err != nil { + return "", fmt.Errorf("failed to read password: %s", err) + } + password := string(rawPassword) + if password == "" { + return "", fmt.Errorf("invalid password") + } + return password, nil +} + +func readPasswordFile(filename string) (string, error) { + f, err := os.Open(filename) + if err != nil { + return "", err + } + defer f.Close() + scanner := bufio.NewScanner(f) + if !scanner.Scan() { + err := scanner.Err() + if err == nil { + err = fmt.Errorf("failed to read from %q: unexpected EOF", + filename) + } + return "", err + } + password := scanner.Text() + if password == "" { + return "", fmt.Errorf("invalid password") + } + return password, nil +} + +func usernamePassword(host, port, username, passwordFile string) (string, string, error) { + // fall back to the system username + if username == "" { + u, err := user.Current() + if err != nil { + return "", "", + fmt.Errorf("failed to obtain username: %s", err) + } + username = u.Username + } + + var password string + var err error + if passwordFile != "" { + password, err = readPasswordFile(passwordFilename) + } else { + password, err = readPassword() + } + if err != nil { + return "", "", err + } + + return username, password, nil +} + +func dialPlainAuth(hostport, username, password string) (*managesieve.Client, error) { + c, err := managesieve.Dial(hostport) + if err != nil { + return nil, fmt.Errorf("failed to connect: %s", err) + } + + host, _, _ := net.SplitHostPort(hostport) + // switch to a TLS connection except for localhost + if host != "localhost" && host != "127.0.0.1" && host != "::1" { + tlsConf := &tls.Config{ + ServerName: host, + InsecureSkipVerify: skipCertVerify, + } + if err := c.StartTLS(tlsConf); err != nil { + return nil, + fmt.Errorf("failed to start TLS connection: %s", + err) + } + } + + auth := managesieve.PlainAuth("", username, password, host) + if err := c.Authenticate(auth); err != nil { + return nil, fmt.Errorf("failed to authenticate user %s: %s", + username, err) + } + + return c, nil +} + +func readLimitedString(r io.Reader, n int64) (string, error) { + var s strings.Builder + _, err := io.CopyN(&s, r, n) + if err == nil { + // check for EOF + _, err = io.CopyN(ioutil.Discard, r, 1) + if err == nil { + return s.String(), errTooBig + } + } + if err != io.EOF { + return s.String(), err + } + + return s.String(), nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/delete.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/delete.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,69 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "net" +) + +func init() { + cmdDelete.Flag.StringVar(&username, "u", "", "Set the username") + cmdDelete.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdDelete = &command{ + UsageLine: "delete [options] host[:port] name", + Run: runDelete, +} + +func runDelete(cmd *command, args []string) error { + if len(args) != 2 { + return usageError("invalid number of arguments") + } + + host, port, err := parseHostPort(args[0]) + if err != nil { + return err + } + + scriptName := args[1] + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + if err := c.DeleteScript(scriptName); err != nil { + return err + } + + return nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/get.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/get.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,72 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "net" + "os" +) + +func init() { + cmdGet.Flag.StringVar(&username, "u", "", "Set the username") + cmdGet.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdGet = &command{ + UsageLine: "get [options] host[:port] name", + Run: runGet, +} + +func runGet(cmd *command, args []string) error { + if len(args) != 2 { + return usageError("invalid number of arguments") + } + + host, port, err := parseHostPort(args[0]) + if err != nil { + return err + } + + scriptName := args[1] + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + script, err := c.GetScript(scriptName) + if err != nil { + return err + } + os.Stdout.WriteString(script) + + return nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/info.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/info.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,84 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "fmt" + "net" +) + +func init() { + cmdInfo.Flag.StringVar(&username, "u", "", "Set the username") + cmdInfo.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdInfo = &command{ + UsageLine: "info [options] host[:port]", + Run: runInfo, +} + +func runInfo(cmd *command, args []string) error { + if len(args) != 1 { + return usageError("invalid number of arguments") + } + + host, port, err := parseHostPort(args[0]) + if err != nil { + return err + } + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + fmt.Println(net.JoinHostPort(host, port)) + if c.SupportsRFC5804() { + fmt.Println("RFC5804 supported") + } + if c.SupportsTLS() { + fmt.Println("STARTTLS supported") + } + fmt.Printf("SASL mechanisms:\n") + for _, m := range c.SASLMechanisms() { + fmt.Printf(" %s\n", m) + } + fmt.Printf("Extensions:\n") + for _, e := range c.Extensions() { + fmt.Printf(" %s\n", e) + } + fmt.Printf("Notification methods:\n") + for _, m := range c.NotifyMethods() { + fmt.Printf(" %s\n", m) + } + + return nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/list.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/list.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,77 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "fmt" + "net" +) + +func init() { + cmdList.Flag.StringVar(&username, "u", "", "Set the username") + cmdList.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdList = &command{ + UsageLine: "list [options] host[:port]", + Run: runList, +} + +func runList(cmd *command, args []string) error { + if len(args) != 1 { + return usageError("invalid number of arguments") + } + + host, port, err := parseHostPort(args[0]) + if err != nil { + return err + } + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + scripts, active, _ := c.ListScripts() + if err != nil { + return err + } + fmt.Println("ACTIVE SCRIPT") + for _, script := range scripts { + isActive := "no" + if script == active { + isActive = "yes" + } + fmt.Printf("%-3s %s\n", isActive, script) + } + + return nil +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/main.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/main.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,116 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "errors" + "flag" + "fmt" + "os" +) + +const ( + exitSuccess = iota + exitFailure + exitUsage +) + +type usageError string + +func (e usageError) Error() string { + return string(e) +} + +var ( + skipCertVerify bool + username string + passwordFilename string +) + +var cmds = []*command{ + cmdList, + cmdPut, + cmdGet, + cmdActivate, + cmdDeactivate, + cmdDelete, + cmdInfo, +} + +func usage() { + fmt.Fprintf(flag.CommandLine.Output(), + "usage:\n %s [options] [subcommand [options] [arguments]]\n", + flag.CommandLine.Name()) + fmt.Fprintln(flag.CommandLine.Output(), "subcommands:") + for _, cmd := range cmds { + fmt.Fprintf(flag.CommandLine.Output(), " %s\n", cmd.Name()) + } + fmt.Fprintln(flag.CommandLine.Output(), "global options:") + flag.PrintDefaults() +} + +func main() { + flag.Usage = usage + flag.BoolVar(&skipCertVerify, "I", false, + "Skip TLS certificate verification") + flag.Parse() + if flag.NArg() == 0 { + fmt.Fprintln(flag.CommandLine.Output(), "missing subcommand") + usage() + os.Exit(exitUsage) + } + + name := flag.Arg(0) + var cmd *command + for _, c := range cmds { + if c.Name() == name { + cmd = c + break + } + } + if cmd == nil { + fmt.Fprintf(flag.CommandLine.Output(), + "unknown subcommand %q\n", name) + usage() + os.Exit(exitUsage) + } + + cmd.Flag.Init(cmd.Name(), flag.ExitOnError) + cmd.Flag.Usage = cmd.Usage + args := flag.Args() + if err := cmd.Flag.Parse(args[1:]); err != nil { + fmt.Fprintln(flag.CommandLine.Output(), err) + os.Exit(exitFailure) + } + + if err := cmd.Run(cmd, cmd.Flag.Args()); err != nil { + fmt.Fprintln(flag.CommandLine.Output(), err) + + var uerr usageError + if errors.As(err, &uerr) { + cmd.Usage() + os.Exit(exitUsage) + } + + os.Exit(exitFailure) + } +} diff -r 000000000000 -r b00673734e58 cmd/sievemgr/put.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/put.go Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,93 @@ +// Copyright (C) 2020 Guido Berhoerster +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +package main + +import ( + "fmt" + "io" + "net" + "os" + + "go.guido-berhoerster.org/managesieve" +) + +func init() { + cmdPut.Flag.StringVar(&username, "u", "", "Set the username") + cmdPut.Flag.StringVar(&passwordFilename, "P", "", + "Set the name of the password file") +} + +var cmdPut = &command{ + UsageLine: "put [options] host[:port] name [file]", + Run: runPut, +} + +func runPut(cmd *command, args []string) error { + var scriptName string + var r io.Reader = os.Stdin + var host, port string + var err error + switch len(args) { + case 3: // name and filename + scriptFile, err := os.Open(args[2]) + if err != nil { + return fmt.Errorf("failed to open script file: %s\n", + err) + } + defer scriptFile.Close() + r = scriptFile + fallthrough + case 2: // only name + host, port, err = parseHostPort(args[0]) + if err != nil { + return err + } + + scriptName = args[1] + default: + return usageError("invalid number of arguments") + } + + script, err := readLimitedString(r, managesieve.ReadLimit) + if err != nil { + return fmt.Errorf("failed to read script: %s\n", err) + } + + username, password, err := usernamePassword(host, port, username, + passwordFilename) + if err != nil { + return err + } + + c, err := dialPlainAuth(net.JoinHostPort(host, port), username, + password) + if err != nil { + return err + } + defer c.Logout() + + if err := c.PutScript(scriptName, script); err != nil { + return err + } + + return nil +} diff -r 000000000000 -r b00673734e58 go.mod --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/go.mod Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,8 @@ +module go.guido-berhoerster.org/sievemgr + +go 1.14 + +require ( + go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 +) diff -r 000000000000 -r b00673734e58 go.sum --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/go.sum Mon Oct 26 14:19:24 2020 +0100 @@ -0,0 +1,12 @@ +go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c h1:4HErjjy83de5F6xWn9Fd+N31KDJh0h1jibO6n0Ye6lw= +go.guido-berhoerster.org/managesieve v0.0.0-20201026131555-3fe1614de42c/go.mod h1:uvqvWGFO2zONQiEQuDNH37Mg2pM7gTsDZSXtBOSmj98= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=