io_tls.c - sacc - sacc - sacc(omys), simple console gopher client (config) | |
git clone git://git.codemadness.org/sacc | |
Log | |
Files | |
Refs | |
LICENSE | |
--- | |
io_tls.c (6002B) | |
--- | |
1 #include <errno.h> | |
2 #include <limits.h> | |
3 #include <pwd.h> | |
4 #include <stdio.h> | |
5 #include <stdlib.h> | |
6 #include <string.h> | |
7 #include <unistd.h> | |
8 #include <netdb.h> | |
9 | |
10 #include <sys/socket.h> | |
11 #include <sys/stat.h> | |
12 | |
13 #include <tls.h> | |
14 | |
15 #include "common.h" | |
16 #include "io.h" | |
17 | |
18 #define TLS_OFF 0 | |
19 #define TLS_ON 1 | |
20 #define TLS_PEM 2 | |
21 | |
22 struct pem { | |
23 char path[PATH_MAX]; | |
24 char *dir; | |
25 char *cert; | |
26 size_t certsz; | |
27 }; | |
28 | |
29 int tls; | |
30 | |
31 static struct pem pem = { .dir = ".share/sacc/cert" }; | |
32 | |
33 static int | |
34 mkpath(char *path, mode_t mode) | |
35 { | |
36 char *s; | |
37 int r; | |
38 | |
39 for (s = path+1; (s = strchr(s, '/')) != NULL; ++s) { | |
40 s[0] = '\0'; | |
41 errno = 0; | |
42 r = mkdir(path, mode); | |
43 s[0] = '/'; | |
44 if (r == -1 && errno != EEXIST) | |
45 return -1; | |
46 }; | |
47 if (mkdir(path, S_IRWXU) == -1 && errno != EEXIST) | |
48 return -1; | |
49 return 0; | |
50 } | |
51 | |
52 static int | |
53 setup_tls(void) | |
54 { | |
55 struct passwd *pw; | |
56 char *p; | |
57 int n; | |
58 | |
59 if ((p = getenv("SACC_CERT_DIR")) != NULL) { | |
60 n = snprintf(pem.path, sizeof(pem.path), "%s/", p); | |
61 if (n < 0 || (unsigned)n >= sizeof(pem.path)) { | |
62 diag("PEM path too long: %s/", p); | |
63 return -1; | |
64 } | |
65 } else { | |
66 if ((pw = getpwuid(geteuid())) == NULL) | |
67 return -1; | |
68 n = snprintf(pem.path, sizeof(pem.path), "%s/%s/", | |
69 pw->pw_dir, pem.dir); | |
70 if (n < 0 || (unsigned)n >= sizeof(pem.path)) { | |
71 diag("PEM path too long: %s/%s/", pw->pw_dir, pe… | |
72 return -1; | |
73 } | |
74 } | |
75 | |
76 if (mkpath(pem.path, S_IRWXU) == -1) { | |
77 diag("Can't create cert dir: %s: %s", | |
78 pem.path, strerror(errno)); | |
79 } else { | |
80 pem.cert = pem.path + n; | |
81 pem.certsz = sizeof(pem.path) - n; | |
82 } | |
83 | |
84 return 0; | |
85 } | |
86 | |
87 static int | |
88 close_tls(struct cnx *c) | |
89 { | |
90 int r; | |
91 | |
92 if (tls != TLS_OFF && c->tls) { | |
93 do { | |
94 r = tls_close(c->tls); | |
95 } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); | |
96 | |
97 tls_free(c->tls); | |
98 } | |
99 | |
100 return close(c->sock); | |
101 } | |
102 | |
103 static int | |
104 savepem(struct tls *t, char *path) | |
105 { | |
106 FILE *f; | |
107 const char *s; | |
108 size_t ln; | |
109 int e = 0; | |
110 | |
111 if (path == NULL) | |
112 return -1; | |
113 if ((s = tls_peer_cert_chain_pem(t, &ln)) == NULL) | |
114 return -1; | |
115 if ((f = fopen(path, "w")) == NULL) | |
116 return -1; | |
117 | |
118 while (ln > 0) | |
119 ln = fwrite(s, 1, ln, f); | |
120 | |
121 if (ferror(f)) | |
122 e = -1; | |
123 if (fclose(f) != 0) | |
124 e = -1; | |
125 if (e == -1) | |
126 unlink(path); | |
127 | |
128 return e; | |
129 } | |
130 | |
131 static char * | |
132 conftls(struct tls *t, const char *host) | |
133 { | |
134 struct tls_config *tc; | |
135 char *p; | |
136 int n; | |
137 | |
138 tc = NULL; | |
139 p = NULL; | |
140 | |
141 if (pem.cert == NULL) | |
142 return NULL; | |
143 | |
144 n = snprintf(pem.cert, pem.certsz, "%s", host); | |
145 if (n < 0 || (unsigned)n >= pem.certsz) { | |
146 diag("PEM path too long: %s/%s", pem.cert, host); | |
147 return NULL; | |
148 } | |
149 | |
150 switch (tls) { | |
151 case TLS_ON: | |
152 /* check if there is a local certificate for target */ | |
153 if (access(pem.path, R_OK) == 0) { | |
154 if ((tc = tls_config_new()) == NULL) | |
155 return NULL; | |
156 if (tls_config_set_ca_file(tc, pem.path) == -1) | |
157 goto end; | |
158 if (tls_configure(t, tc) == -1) | |
159 goto end; | |
160 p = pem.path; | |
161 } | |
162 break; | |
163 case TLS_PEM: | |
164 /* save target certificate to file */ | |
165 if ((tc = tls_config_new()) == NULL) | |
166 return NULL; | |
167 tls_config_insecure_noverifycert(tc); | |
168 if (tls_configure(t, tc) == -1) | |
169 goto end; | |
170 p = pem.path; | |
171 break; | |
172 } | |
173 end: | |
174 tls_config_free(tc); | |
175 return p; | |
176 } | |
177 | |
178 static int | |
179 connect_tls(struct cnx *c, struct addrinfo *ai, const char *host) | |
180 { | |
181 struct tls *t; | |
182 char *s, *pempath; | |
183 int r; | |
184 | |
185 c->tls = NULL; | |
186 s = NULL; | |
187 r = CONN_ERROR; | |
188 | |
189 if (connect(c->sock, ai->ai_addr, ai->ai_addrlen) == -1) | |
190 return r; | |
191 | |
192 if (!tls) | |
193 return CONN_VALID; | |
194 | |
195 if ((t = tls_client()) == NULL) | |
196 return r; | |
197 | |
198 pempath = conftls(t, host); | |
199 | |
200 if (tls_connect_socket(t, c->sock, host) == -1) | |
201 goto end; | |
202 | |
203 do { | |
204 r = tls_handshake(t); | |
205 } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); | |
206 | |
207 if (r == 0) { | |
208 switch (tls) { | |
209 case TLS_ON: | |
210 c->tls = t; | |
211 break; | |
212 case TLS_PEM: | |
213 r = savepem(t, pempath) == 0 ? CONN_RETRY : CONN… | |
214 tls = TLS_ON; | |
215 break; | |
216 } | |
217 } else { | |
218 diag("Can't establish TLS with \"%s\": %s", | |
219 host, tls_error(t)); | |
220 | |
221 if (!interactive) { | |
222 r = CONN_ABORT; | |
223 goto end; | |
224 } | |
225 | |
226 if (pem.cert) { | |
227 s = uiprompt("Save certificate locally and retry… | |
228 switch (*s) { | |
229 case 'Y': | |
230 case 'y': | |
231 tls = TLS_PEM; | |
232 r = CONN_RETRY; | |
233 goto end; | |
234 } | |
235 } | |
236 | |
237 s = uiprompt("Retry on cleartext? [Yn]: "); | |
238 switch (*s) { | |
239 case 'Y': | |
240 case 'y': | |
241 case '\0': | |
242 tls = TLS_OFF; | |
243 r = CONN_RETRY; | |
244 break; | |
245 default: | |
246 r = CONN_ABORT; | |
247 } | |
248 } | |
249 end: | |
250 free(s); | |
251 if (r != CONN_VALID) | |
252 tls_free(t); | |
253 | |
254 return r; | |
255 } | |
256 | |
257 static void | |
258 connerr_tls(struct cnx *c, const char *host, const char *port, int err) | |
259 { | |
260 if (c->sock == -1) { | |
261 diag("Can't open socket: %s", strerror(err)); | |
262 } else { | |
263 if (tls != TLS_OFF && c->tls) { | |
264 diag("Can't establish TLS with \"%s\": %s", | |
265 host, tls_error(c->tls)); | |
266 } else { | |
267 diag("Can't connect to: %s:%s: %s", host, port, | |
268 strerror(err)); | |
269 } | |
270 } | |
271 } | |
272 | |
273 static char * | |
274 parseurl_tls(char *url) | |
275 { | |
276 char *p; | |
277 | |
278 if (p = strstr(url, "://")) { | |
279 if (!strncmp(url, "gopher", p - url)) { | |
280 if (tls) | |
281 diag("Switching from gophers to gopher"); | |
282 tls = TLS_OFF; | |
283 } else if (!strncmp(url, "gophers", p - url)) { | |
284 tls = TLS_ON; | |
285 } else { | |
286 die("Protocol not supported: %.*s", p - url, url… | |
287 } | |
288 url = p + 3; | |
289 } | |
290 | |
291 return url; | |
292 } | |
293 | |
294 static ssize_t | |
295 read_tls(struct cnx *c, void *buf, size_t bs) | |
296 { | |
297 ssize_t n; | |
298 | |
299 if (tls != TLS_OFF && c->tls) { | |
300 do { | |
301 n = tls_read(c->tls, buf, bs); | |
302 } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); | |
303 } else { | |
304 n = read(c->sock, buf, bs); | |
305 } | |
306 | |
307 return n; | |
308 } | |
309 | |
310 static ssize_t | |
311 write_tls(struct cnx *c, void *buf, size_t bs) | |
312 { | |
313 ssize_t n; | |
314 | |
315 if (tls) { | |
316 do { | |
317 n = tls_write(c->tls, buf, bs); | |
318 } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); | |
319 } else { | |
320 n = write(c->sock, buf, bs); | |
321 } | |
322 | |
323 return n; | |
324 } | |
325 | |
326 int (*iosetup)(void) = setup_tls; | |
327 int (*ioclose)(struct cnx *) = close_tls; | |
328 int (*ioconnect)(struct cnx *, struct addrinfo *, const char *) = connec… | |
329 void (*ioconnerr)(struct cnx *, const char *, const char *, int) = conne… | |
330 char *(*ioparseurl)(char *) = parseurl_tls; | |
331 ssize_t (*ioread)(struct cnx *, void *, size_t) = read_tls; | |
332 ssize_t (*iowrite)(struct cnx *, void *, size_t) = write_tls; |