sacc

sacc (saccomys): simple gopher client.
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, 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;