Recently Written · git

sacc

Mirror of git://bitreich.org/sacc, the sacc gopher client from bitreich.org.

git clone https://github.com/equwal/sacc

Log | Files | Refs


io_tls.c (6002 bytes)

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, pem.dir);
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_ERROR;
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? [yN]: ");
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 *) = connect_tls;
329 void (*ioconnerr)(struct cnx *, const char *, const char *, int) = connerr_tls;
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;