Mercurial > projects > managesieve
diff cmd/sievemgr/common.go @ 0:6369453d47a3
Initial revision
author | Guido Berhoerster <guido+managesieve@berhoerster.name> |
---|---|
date | Thu, 15 Oct 2020 09:11:05 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cmd/sievemgr/common.go Thu Oct 15 09:11:05 2020 +0200 @@ -0,0 +1,192 @@ +// Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name> +// +// 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 +}