sacc

sacc (saccomys): simple gopher client.
Log | Files | Refs | 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, 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 
    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_ERROR;
    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? [yN]: ");
    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 *) = connect_tls;
    321 void (*ioconnerr)(struct cnx *, const char *, const char *, int) = connerr_tls;
    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;