comparison cmd/sievemgr/common.go @ 0:b00673734e58

Initial revision Split off sievemgr command from the managesieve repository.
author Guido Berhoerster <guido+sievemgr@berhoerster.name>
date Mon, 26 Oct 2020 14:19:24 +0100
parents
children 0cd5a454dfb4
comparison
equal deleted inserted replaced
-1:000000000000 0:b00673734e58
1 // Copyright (C) 2020 Guido Berhoerster <guido+managesieve@berhoerster.name>
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included
12 // in all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 package main
23
24 import (
25 "bufio"
26 "crypto/tls"
27 "errors"
28 "fmt"
29 "io"
30 "io/ioutil"
31 "net"
32 "os"
33 "os/user"
34 "runtime"
35 "strings"
36
37 "go.guido-berhoerster.org/managesieve"
38 "golang.org/x/crypto/ssh/terminal"
39 )
40
41 var errTooBig = errors.New("too big")
42
43 func parseHostPort(s string) (string, string, error) {
44 var host string
45 host, port, err := net.SplitHostPort(s)
46 if err != nil {
47 // error may be due to a missing port but there is no usable
48 // error value to test for, thus try again with a port added
49 var tmpErr error
50 host, _, tmpErr = net.SplitHostPort(s + ":4190")
51 if tmpErr != nil {
52 return "", "", err
53 }
54 }
55 if port == "" {
56 // no port given, try to look up a SRV record for given domain
57 // and fall back to the domain and port 4190
58 services, err := managesieve.LookupService(host)
59 if err != nil {
60 return "", "", err
61 }
62 host, port, err = net.SplitHostPort(services[0])
63 if err != nil {
64 return "", "", err
65 }
66 }
67 return host, port, nil
68 }
69
70 func readPassword() (string, error) {
71 var tty *os.File
72 var fd int
73 var w io.Writer
74 if runtime.GOOS == "windows" {
75 fd = int(os.Stdin.Fd())
76 w = os.Stdout
77 } else {
78 var err error
79 tty, err = os.OpenFile("/dev/tty", os.O_RDWR, 0666)
80 if err != nil {
81 return "", err
82 }
83 defer tty.Close()
84 fd = int(tty.Fd())
85 w = tty
86 }
87
88 io.WriteString(w, "Password: ")
89 rawPassword, err := terminal.ReadPassword(fd)
90 io.WriteString(w, "\n")
91 if err != nil {
92 return "", fmt.Errorf("failed to read password: %s", err)
93 }
94 password := string(rawPassword)
95 if password == "" {
96 return "", fmt.Errorf("invalid password")
97 }
98 return password, nil
99 }
100
101 func readPasswordFile(filename string) (string, error) {
102 f, err := os.Open(filename)
103 if err != nil {
104 return "", err
105 }
106 defer f.Close()
107 scanner := bufio.NewScanner(f)
108 if !scanner.Scan() {
109 err := scanner.Err()
110 if err == nil {
111 err = fmt.Errorf("failed to read from %q: unexpected EOF",
112 filename)
113 }
114 return "", err
115 }
116 password := scanner.Text()
117 if password == "" {
118 return "", fmt.Errorf("invalid password")
119 }
120 return password, nil
121 }
122
123 func usernamePassword(host, port, username, passwordFile string) (string, string, error) {
124 // fall back to the system username
125 if username == "" {
126 u, err := user.Current()
127 if err != nil {
128 return "", "",
129 fmt.Errorf("failed to obtain username: %s", err)
130 }
131 username = u.Username
132 }
133
134 var password string
135 var err error
136 if passwordFile != "" {
137 password, err = readPasswordFile(passwordFilename)
138 } else {
139 password, err = readPassword()
140 }
141 if err != nil {
142 return "", "", err
143 }
144
145 return username, password, nil
146 }
147
148 func dialPlainAuth(hostport, username, password string) (*managesieve.Client, error) {
149 c, err := managesieve.Dial(hostport)
150 if err != nil {
151 return nil, fmt.Errorf("failed to connect: %s", err)
152 }
153
154 host, _, _ := net.SplitHostPort(hostport)
155 // switch to a TLS connection except for localhost
156 if host != "localhost" && host != "127.0.0.1" && host != "::1" {
157 tlsConf := &tls.Config{
158 ServerName: host,
159 InsecureSkipVerify: skipCertVerify,
160 }
161 if err := c.StartTLS(tlsConf); err != nil {
162 return nil,
163 fmt.Errorf("failed to start TLS connection: %s",
164 err)
165 }
166 }
167
168 auth := managesieve.PlainAuth("", username, password, host)
169 if err := c.Authenticate(auth); err != nil {
170 return nil, fmt.Errorf("failed to authenticate user %s: %s",
171 username, err)
172 }
173
174 return c, nil
175 }
176
177 func readLimitedString(r io.Reader, n int64) (string, error) {
178 var s strings.Builder
179 _, err := io.CopyN(&s, r, n)
180 if err == nil {
181 // check for EOF
182 _, err = io.CopyN(ioutil.Discard, r, 1)
183 if err == nil {
184 return s.String(), errTooBig
185 }
186 }
187 if err != io.EOF {
188 return s.String(), err
189 }
190
191 return s.String(), nil
192 }