Mercurial > projects > sievemgr
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 } |