io_tls.c - sacc - sacc(omys), simple console gopher client | |
git clone git://bitreich.org/sacc/ git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65… | |
Log | |
Files | |
Refs | |
Tags | |
LICENSE | |
--- | |
io_tls.c (5921B) | |
--- | |
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 | |
110 if (path == NULL) | |
111 return -1; | |
112 if ((s = tls_peer_cert_chain_pem(t, &ln)) == NULL) | |
113 return -1; | |
114 if ((f = fopen(path, "w")) == NULL) | |
115 return -1; | |
116 fprintf(f, "%.*s\n", ln, s); | |
117 if (fclose(f) != 0) | |
118 return -1; | |
119 | |
120 return 0; | |
121 } | |
122 | |
123 static char * | |
124 conftls(struct tls *t, const char *host) | |
125 { | |
126 struct tls_config *tc; | |
127 char *p; | |
128 int n; | |
129 | |
130 tc = NULL; | |
131 p = NULL; | |
132 | |
133 if (pem.cert == NULL) | |
134 return NULL; | |
135 | |
136 n = snprintf(pem.cert, pem.certsz, "%s", host); | |
137 if (n < 0 || (unsigned)n >= pem.certsz) { | |
138 diag("PEM path too long: %s/%s", pem.cert, host); | |
139 return NULL; | |
140 } | |
141 | |
142 switch (tls) { | |
143 case TLS_ON: | |
144 /* check if there is a local certificate for target */ | |
145 if (access(pem.path, R_OK) == 0) { | |
146 if ((tc = tls_config_new()) == NULL) | |
147 return NULL; | |
148 if (tls_config_set_ca_file(tc, pem.path) == -1) | |
149 goto end; | |
150 if (tls_configure(t, tc) == -1) | |
151 goto end; | |
152 p = pem.path; | |
153 } | |
154 break; | |
155 case TLS_PEM: | |
156 /* save target certificate to file */ | |
157 if ((tc = tls_config_new()) == NULL) | |
158 return NULL; | |
159 tls_config_insecure_noverifycert(tc); | |
160 if (tls_configure(t, tc) == -1) | |
161 goto end; | |
162 p = pem.path; | |
163 break; | |
164 } | |
165 end: | |
166 tls_config_free(tc); | |
167 return p; | |
168 } | |
169 | |
170 static int | |
171 connect_tls(struct cnx *c, struct addrinfo *ai, const char *host) | |
172 { | |
173 struct tls *t; | |
174 char *s, *pempath; | |
175 int r; | |
176 | |
177 c->tls = NULL; | |
178 s = NULL; | |
179 r = CONN_ERROR; | |
180 | |
181 if (connect(c->sock, ai->ai_addr, ai->ai_addrlen) == -1) | |
182 return r; | |
183 | |
184 if (!tls) | |
185 return CONN_VALID; | |
186 | |
187 if ((t = tls_client()) == NULL) | |
188 return r; | |
189 | |
190 pempath = conftls(t, host); | |
191 | |
192 if (tls_connect_socket(t, c->sock, host) == -1) | |
193 goto end; | |
194 | |
195 do { | |
196 r = tls_handshake(t); | |
197 } while (r == TLS_WANT_POLLIN || r == TLS_WANT_POLLOUT); | |
198 | |
199 if (r == 0) { | |
200 switch (tls) { | |
201 case TLS_ON: | |
202 c->tls = t; | |
203 break; | |
204 case TLS_PEM: | |
205 r = savepem(t, pempath) == 0 ? CONN_RETRY : CONN… | |
206 tls = TLS_ON; | |
207 break; | |
208 } | |
209 } else { | |
210 diag("Can't establish TLS with \"%s\": %s", | |
211 host, tls_error(t)); | |
212 | |
213 if (!interactive) { | |
214 r = CONN_ABORT; | |
215 goto end; | |
216 } | |
217 | |
218 if (pem.cert) { | |
219 s = uiprompt("Save certificate locally and retry… | |
220 switch (*s) { | |
221 case 'Y': | |
222 case 'y': | |
223 tls = TLS_PEM; | |
224 r = CONN_RETRY; | |
225 goto end; | |
226 } | |
227 } | |
228 | |
229 s = uiprompt("Retry on cleartext? [Yn]: "); | |
230 switch (*s) { | |
231 case 'Y': | |
232 case 'y': | |
233 case '\0': | |
234 tls = TLS_OFF; | |
235 r = CONN_RETRY; | |
236 break; | |
237 default: | |
238 r = CONN_ABORT; | |
239 } | |
240 } | |
241 end: | |
242 free(s); | |
243 if (r != CONN_VALID) | |
244 tls_free(t); | |
245 | |
246 return r; | |
247 } | |
248 | |
249 static void | |
250 connerr_tls(struct cnx *c, const char *host, const char *port, int err) | |
251 { | |
252 if (c->sock == -1) { | |
253 diag("Can't open socket: %s", strerror(err)); | |
254 } else { | |
255 if (tls != TLS_OFF && c->tls) { | |
256 diag("Can't establish TLS with \"%s\": %s", | |
257 host, tls_error(c->tls)); | |
258 } else { | |
259 diag("Can't connect to: %s:%s: %s", host, port, | |
260 strerror(err)); | |
261 } | |
262 } | |
263 } | |
264 | |
265 static char * | |
266 parseurl_tls(char *url) | |
267 { | |
268 char *p; | |
269 | |
270 if (p = strstr(url, "://")) { | |
271 if (!strncmp(url, "gopher", p - url)) { | |
272 if (tls) | |
273 diag("Switching from gophers to gopher"); | |
274 tls = TLS_OFF; | |
275 } else if (!strncmp(url, "gophers", p - url)) { | |
276 tls = TLS_ON; | |
277 } else { | |
278 die("Protocol not supported: %.*s", p - url, url… | |
279 } | |
280 url = p + 3; | |
281 } | |
282 | |
283 return url; | |
284 } | |
285 | |
286 static ssize_t | |
287 read_tls(struct cnx *c, void *buf, size_t bs) | |
288 { | |
289 ssize_t n; | |
290 | |
291 if (tls != TLS_OFF && c->tls) { | |
292 do { | |
293 n = tls_read(c->tls, buf, bs); | |
294 } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); | |
295 } else { | |
296 n = read(c->sock, buf, bs); | |
297 } | |
298 | |
299 return n; | |
300 } | |
301 | |
302 static ssize_t | |
303 write_tls(struct cnx *c, void *buf, size_t bs) | |
304 { | |
305 ssize_t n; | |
306 | |
307 if (tls) { | |
308 do { | |
309 n = tls_write(c->tls, buf, bs); | |
310 } while (n == TLS_WANT_POLLIN || n == TLS_WANT_POLLOUT); | |
311 } else { | |
312 n = write(c->sock, buf, bs); | |
313 } | |
314 | |
315 return n; | |
316 } | |
317 | |
318 int (*iosetup)(void) = setup_tls; | |
319 int (*ioclose)(struct cnx *) = close_tls; | |
320 int (*ioconnect)(struct cnx *, struct addrinfo *, const char *) = connec… | |
321 void (*ioconnerr)(struct cnx *, const char *, const char *, int) = conne… | |
322 char *(*ioparseurl)(char *) = parseurl_tls; | |
323 ssize_t (*ioread)(struct cnx *, void *, size_t) = read_tls; | |
324 ssize_t (*iowrite)(struct cnx *, void *, size_t) = write_tls; |