xref: /netbsd-src/external/bsd/fetch/dist/libfetch/common.c (revision 3f351f34c6d827cf017cdcff3543f6ec0c88b420)
1 /*	$NetBSD: common.c,v 1.6 2024/01/03 11:40:38 riastradh Exp $	*/
2 /*-
3  * Copyright (c) 1998-2004 Dag-Erling Co�dan Sm�rgrav
4  * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org>
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer
12  *    in this position and unchanged.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. The name of the author may not be used to endorse or promote products
17  *    derived from this software without specific prior written permission
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  *
30  * $FreeBSD: common.c,v 1.53 2007/12/19 00:26:36 des Exp $
31  */
32 
33 #if HAVE_CONFIG_H
34 #include "config.h"
35 #endif
36 #ifndef NETBSD
37 #include <nbcompat.h>
38 #endif
39 
40 #include <sys/types.h>
41 #include <sys/socket.h>
42 #include <sys/time.h>
43 #include <sys/uio.h>
44 
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47 
48 #include <ctype.h>
49 #include <errno.h>
50 #if defined(HAVE_INTTYPES_H) || defined(NETBSD)
51 #include <inttypes.h>
52 #endif
53 #ifndef NETBSD
54 #include <nbcompat/netdb.h>
55 #else
56 #include <netdb.h>
57 #endif
58 #include <pwd.h>
59 #include <stdarg.h>
60 #include <stdlib.h>
61 #include <stdio.h>
62 #include <string.h>
63 #include <unistd.h>
64 
65 #ifndef MSG_NOSIGNAL
66 #include <signal.h>
67 #endif
68 
69 #include "fetch.h"
70 #include "common.h"
71 
72 /*** Local data **************************************************************/
73 
74 /*
75  * Error messages for resolver errors
76  */
77 static struct fetcherr netdb_errlist[] = {
78 #ifdef EAI_NODATA
79 	{ EAI_NODATA,	FETCH_RESOLV,	"Host not found" },
80 #endif
81 	{ EAI_AGAIN,	FETCH_TEMP,	"Transient resolver failure" },
82 	{ EAI_FAIL,	FETCH_RESOLV,	"Non-recoverable resolver failure" },
83 	{ EAI_NONAME,	FETCH_RESOLV,	"No address record" },
84 	{ -1,		FETCH_UNKNOWN,	"Unknown resolver error" }
85 };
86 
87 /*** Error-reporting functions ***********************************************/
88 
89 /*
90  * Map error code to string
91  */
92 static struct fetcherr *
93 fetch_finderr(struct fetcherr *p, int e)
94 {
95 	while (p->num != -1 && p->num != e)
96 		p++;
97 	return (p);
98 }
99 
100 /*
101  * Set error code
102  */
103 void
104 fetch_seterr(struct fetcherr *p, int e)
105 {
106 	p = fetch_finderr(p, e);
107 	fetchLastErrCode = p->cat;
108 	snprintf(fetchLastErrString, MAXERRSTRING, "%s", p->string);
109 }
110 
111 /*
112  * Set error code according to errno
113  */
114 void
115 fetch_syserr(void)
116 {
117 	switch (errno) {
118 	case 0:
119 		fetchLastErrCode = FETCH_OK;
120 		break;
121 	case EPERM:
122 	case EACCES:
123 	case EROFS:
124 #ifdef EAUTH
125 	case EAUTH:
126 #endif
127 #ifdef ENEEDAUTH
128 	case ENEEDAUTH:
129 #endif
130 		fetchLastErrCode = FETCH_AUTH;
131 		break;
132 	case ENOENT:
133 	case EISDIR: /* XXX */
134 		fetchLastErrCode = FETCH_UNAVAIL;
135 		break;
136 	case ENOMEM:
137 		fetchLastErrCode = FETCH_MEMORY;
138 		break;
139 	case EBUSY:
140 	case EAGAIN:
141 		fetchLastErrCode = FETCH_TEMP;
142 		break;
143 	case EEXIST:
144 		fetchLastErrCode = FETCH_EXISTS;
145 		break;
146 	case ENOSPC:
147 		fetchLastErrCode = FETCH_FULL;
148 		break;
149 	case EADDRINUSE:
150 	case EADDRNOTAVAIL:
151 	case ENETDOWN:
152 	case ENETUNREACH:
153 	case ENETRESET:
154 	case EHOSTUNREACH:
155 		fetchLastErrCode = FETCH_NETWORK;
156 		break;
157 	case ECONNABORTED:
158 	case ECONNRESET:
159 		fetchLastErrCode = FETCH_ABORT;
160 		break;
161 	case ETIMEDOUT:
162 		fetchLastErrCode = FETCH_TIMEOUT;
163 		break;
164 	case ECONNREFUSED:
165 	case EHOSTDOWN:
166 		fetchLastErrCode = FETCH_DOWN;
167 		break;
168 default:
169 		fetchLastErrCode = FETCH_UNKNOWN;
170 	}
171 	snprintf(fetchLastErrString, MAXERRSTRING, "%s", strerror(errno));
172 }
173 
174 
175 /*
176  * Emit status message
177  */
178 void
179 fetch_info(const char *fmt, ...)
180 {
181 	va_list ap;
182 
183 	va_start(ap, fmt);
184 	vfprintf(stderr, fmt, ap);
185 	va_end(ap);
186 	fputc('\n', stderr);
187 }
188 
189 
190 /*** Network-related utility functions ***************************************/
191 
192 /*
193  * Return the default port for a scheme
194  */
195 int
196 fetch_default_port(const char *scheme)
197 {
198 	struct servent *se;
199 
200 	if ((se = getservbyname(scheme, "tcp")) != NULL)
201 		return (ntohs(se->s_port));
202 	if (strcasecmp(scheme, SCHEME_FTP) == 0)
203 		return (FTP_DEFAULT_PORT);
204 	if (strcasecmp(scheme, SCHEME_HTTP) == 0)
205 		return (HTTP_DEFAULT_PORT);
206 	return (0);
207 }
208 
209 /*
210  * Return the default proxy port for a scheme
211  */
212 int
213 fetch_default_proxy_port(const char *scheme)
214 {
215 	if (strcasecmp(scheme, SCHEME_FTP) == 0)
216 		return (FTP_DEFAULT_PROXY_PORT);
217 	if (strcasecmp(scheme, SCHEME_HTTP) == 0)
218 		return (HTTP_DEFAULT_PROXY_PORT);
219 	return (0);
220 }
221 
222 
223 /*
224  * Create a connection for an existing descriptor.
225  */
226 conn_t *
227 fetch_reopen(int sd)
228 {
229 	conn_t *conn;
230 
231 	/* allocate and fill connection structure */
232 	if ((conn = calloc(1, sizeof(*conn))) == NULL)
233 		return (NULL);
234 	conn->ftp_home = NULL;
235 	conn->cache_url = NULL;
236 	conn->next_buf = NULL;
237 	conn->next_len = 0;
238 	conn->sd = sd;
239 	return (conn);
240 }
241 
242 
243 /*
244  * Bind a socket to a specific local address
245  */
246 int
247 fetch_bind(int sd, int af, const char *addr)
248 {
249 	struct addrinfo hints, *res, *res0;
250 
251 	memset(&hints, 0, sizeof(hints));
252 	hints.ai_family = af;
253 	hints.ai_socktype = SOCK_STREAM;
254 	hints.ai_protocol = 0;
255 	if (getaddrinfo(addr, NULL, &hints, &res0))
256 		return (-1);
257 	for (res = res0; res; res = res->ai_next) {
258 		if (bind(sd, res->ai_addr, res->ai_addrlen) == 0)
259 			return (0);
260 	}
261 	return (-1);
262 }
263 
264 
265 /*
266  * Establish a TCP connection to the specified port on the specified host.
267  */
268 conn_t *
269 fetch_connect(struct url *url, int af, int verbose)
270 {
271 	conn_t *conn;
272 	char pbuf[10];
273 	const char *bindaddr;
274 	struct addrinfo hints, *res, *res0;
275 	int sd, error;
276 
277 	if (verbose)
278 		fetch_info("looking up %s", url->host);
279 
280 	/* look up host name and set up socket address structure */
281 	snprintf(pbuf, sizeof(pbuf), "%d", url->port);
282 	memset(&hints, 0, sizeof(hints));
283 	hints.ai_family = af;
284 	hints.ai_socktype = SOCK_STREAM;
285 	hints.ai_protocol = 0;
286 	if ((error = getaddrinfo(url->host, pbuf, &hints, &res0)) != 0) {
287 		netdb_seterr(error);
288 		return (NULL);
289 	}
290 	bindaddr = getenv("FETCH_BIND_ADDRESS");
291 
292 	if (verbose)
293 		fetch_info("connecting to %s:%d", url->host, url->port);
294 
295 	/* try to connect */
296 	for (sd = -1, res = res0; res; sd = -1, res = res->ai_next) {
297 		if ((sd = socket(res->ai_family, res->ai_socktype,
298 			 res->ai_protocol)) == -1)
299 			continue;
300 		if (bindaddr != NULL && *bindaddr != '\0' &&
301 		    fetch_bind(sd, res->ai_family, bindaddr) != 0) {
302 			fetch_info("failed to bind to '%s'", bindaddr);
303 			close(sd);
304 			continue;
305 		}
306 		if (connect(sd, res->ai_addr, res->ai_addrlen) == 0)
307 			break;
308 		close(sd);
309 	}
310 	freeaddrinfo(res0);
311 	if (sd == -1) {
312 		fetch_syserr();
313 		return (NULL);
314 	}
315 
316 	if ((conn = fetch_reopen(sd)) == NULL) {
317 		fetch_syserr();
318 		close(sd);
319 	}
320 	conn->cache_url = fetchCopyURL(url);
321 	conn->cache_af = af;
322 	return (conn);
323 }
324 
325 static conn_t *connection_cache;
326 static int cache_global_limit = 0;
327 static int cache_per_host_limit = 0;
328 
329 /*
330  * Initialise cache with the given limits.
331  */
332 void
333 fetchConnectionCacheInit(int global_limit, int per_host_limit)
334 {
335 
336 	if (global_limit < 0)
337 		cache_global_limit = INT_MAX;
338 	else if (per_host_limit > global_limit)
339 		cache_global_limit = per_host_limit;
340 	else
341 		cache_global_limit = global_limit;
342 	if (per_host_limit < 0)
343 		cache_per_host_limit = INT_MAX;
344 	else
345 		cache_per_host_limit = per_host_limit;
346 }
347 
348 /*
349  * Flush cache and free all associated resources.
350  */
351 void
352 fetchConnectionCacheClose(void)
353 {
354 	conn_t *conn;
355 
356 	while ((conn = connection_cache) != NULL) {
357 		connection_cache = conn->next_cached;
358 		(*conn->cache_close)(conn);
359 	}
360 }
361 
362 /*
363  * Check connection cache for an existing entry matching
364  * protocol/host/port/user/password/family.
365  */
366 conn_t *
367 fetch_cache_get(const struct url *url, int af)
368 {
369 	conn_t *conn, *last_conn = NULL;
370 
371 	for (conn = connection_cache; conn; last_conn = conn,
372 	    conn = conn->next_cached)
373 	{
374 		if (conn->cache_url->port == url->port &&
375 		    strcmp(conn->cache_url->scheme, url->scheme) == 0 &&
376 		    strcmp(conn->cache_url->host, url->host) == 0 &&
377 		    strcmp(conn->cache_url->user, url->user) == 0 &&
378 		    strcmp(conn->cache_url->pwd, url->pwd) == 0 &&
379 		    (conn->cache_af == AF_UNSPEC || af == AF_UNSPEC ||
380 		     conn->cache_af == af)) {
381 			if (last_conn != NULL)
382 				last_conn->next_cached = conn->next_cached;
383 			else
384 				connection_cache = conn->next_cached;
385 			return conn;
386 		}
387 	}
388 
389 	return NULL;
390 }
391 
392 /*
393  * Put the connection back into the cache for reuse.
394  * If the connection is freed due to LRU or if the cache
395  * is explicitly closed, the given callback is called.
396  */
397 void
398 fetch_cache_put(conn_t *conn, int (*closecb)(conn_t *))
399 {
400 	conn_t *iter, *last, *oiter;
401 	int global_count, host_count;
402 
403 	if (conn->cache_url == NULL || cache_global_limit == 0) {
404 		(*closecb)(conn);
405 		return;
406 	}
407 
408 	global_count = host_count = 0;
409 	last = NULL;
410 	for (iter = connection_cache; iter; ) {
411 		++global_count;
412 		if (strcmp(conn->cache_url->host, iter->cache_url->host) == 0)
413 			++host_count;
414 		if (global_count < cache_global_limit &&
415 		    host_count < cache_per_host_limit)
416 			oiter = NULL;
417 		else {
418 			--global_count;
419 			if (last != NULL)
420 				last->next_cached = iter->next_cached;
421 			else
422 				connection_cache = iter->next_cached;
423 			oiter = iter;
424 		}
425 		last = iter;
426 		iter = iter->next_cached;
427 		if (oiter)
428 			(*oiter->cache_close)(oiter);
429 	}
430 
431 	conn->cache_close = closecb;
432 	conn->next_cached = connection_cache;
433 	connection_cache = conn;
434 }
435 
436 /*
437  * Enable SSL on a connection.
438  */
439 int
440 fetch_ssl(conn_t *conn, int verbose)
441 {
442 
443 #ifdef WITH_SSL
444 	/* Init the SSL library and context */
445 	if (!SSL_library_init()){
446 		fprintf(stderr, "SSL library init failed\n");
447 		return (-1);
448 	}
449 
450 	SSL_load_error_strings();
451 
452 	conn->ssl_meth = SSLv23_client_method();
453 	conn->ssl_ctx = SSL_CTX_new(conn->ssl_meth);
454 	SSL_CTX_set_mode(conn->ssl_ctx, SSL_MODE_AUTO_RETRY);
455 	if (getenv("SSL_NO_VERIFY_PEER") == NULL) {
456 		SSL_CTX_set_default_verify_paths(conn->ssl_ctx);
457 		SSL_CTX_set_verify(conn->ssl_ctx, SSL_VERIFY_PEER, NULL);
458 	}
459 
460 	conn->ssl = SSL_new(conn->ssl_ctx);
461 	if (conn->ssl == NULL){
462 		fprintf(stderr, "SSL context creation failed\n");
463 		return (-1);
464 	}
465 	SSL_set_fd(conn->ssl, conn->sd);
466 	if (!SSL_set_tlsext_host_name(conn->ssl, conn->cache_url->host)) {
467 		fprintf(stderr, "SSL hostname setting failed\n");
468 		return (-1);
469 	}
470 	if (SSL_connect(conn->ssl) == -1){
471 		ERR_print_errors_fp(stderr);
472 		return (-1);
473 	}
474 
475 	if (verbose) {
476 		X509_NAME *name;
477 		char *str;
478 
479 		fprintf(stderr, "SSL connection established using %s\n",
480 		    SSL_get_cipher(conn->ssl));
481 		conn->ssl_cert = SSL_get_peer_certificate(conn->ssl);
482 		name = X509_get_subject_name(conn->ssl_cert);
483 		str = X509_NAME_oneline(name, 0, 0);
484 		printf("Certificate subject: %s\n", str);
485 		free(str);
486 		name = X509_get_issuer_name(conn->ssl_cert);
487 		str = X509_NAME_oneline(name, 0, 0);
488 		printf("Certificate issuer: %s\n", str);
489 		free(str);
490 	}
491 
492 	return (0);
493 #else
494 	(void)conn;
495 	(void)verbose;
496 	fprintf(stderr, "SSL support disabled\n");
497 	return (-1);
498 #endif
499 }
500 
501 
502 /*
503  * Read a character from a connection w/ timeout
504  */
505 ssize_t
506 fetch_read(conn_t *conn, char *buf, size_t len)
507 {
508 	struct timeval now, timeout, waittv;
509 	fd_set readfds;
510 	ssize_t rlen;
511 	int r;
512 
513 	if (len == 0)
514 		return 0;
515 
516 	if (conn->next_len != 0) {
517 		if (conn->next_len < len)
518 			len = conn->next_len;
519 		memmove(buf, conn->next_buf, len);
520 		conn->next_len -= len;
521 		conn->next_buf += len;
522 		return len;
523 	}
524 
525 	if (fetchTimeout) {
526 		FD_ZERO(&readfds);
527 		gettimeofday(&timeout, NULL);
528 		timeout.tv_sec += fetchTimeout;
529 	}
530 
531 	for (;;) {
532 		while (fetchTimeout && !FD_ISSET(conn->sd, &readfds)) {
533 			FD_SET(conn->sd, &readfds);
534 			gettimeofday(&now, NULL);
535 			waittv.tv_sec = timeout.tv_sec - now.tv_sec;
536 			waittv.tv_usec = timeout.tv_usec - now.tv_usec;
537 			if (waittv.tv_usec < 0) {
538 				waittv.tv_usec += 1000000;
539 				waittv.tv_sec--;
540 			}
541 			if (waittv.tv_sec < 0) {
542 				errno = ETIMEDOUT;
543 				fetch_syserr();
544 				return (-1);
545 			}
546 			errno = 0;
547 			r = select(conn->sd + 1, &readfds, NULL, NULL, &waittv);
548 			if (r == -1) {
549 				if (errno == EINTR && fetchRestartCalls)
550 					continue;
551 				fetch_syserr();
552 				return (-1);
553 			}
554 		}
555 #ifdef WITH_SSL
556 		if (conn->ssl != NULL)
557 			rlen = SSL_read(conn->ssl, buf, (int)len);
558 		else
559 #endif
560 			rlen = read(conn->sd, buf, len);
561 		if (rlen >= 0)
562 			break;
563 
564 		if (errno != EINTR || !fetchRestartCalls)
565 			return (-1);
566 	}
567 	return (rlen);
568 }
569 
570 
571 /*
572  * Read a line of text from a connection w/ timeout
573  */
574 #define MIN_BUF_SIZE 1024
575 
576 int
577 fetch_getln(conn_t *conn)
578 {
579 	char *tmp, *next;
580 	size_t tmpsize;
581 	ssize_t len;
582 
583 	if (conn->buf == NULL) {
584 		if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) {
585 			errno = ENOMEM;
586 			return (-1);
587 		}
588 		conn->bufsize = MIN_BUF_SIZE;
589 	}
590 
591 	conn->buflen = 0;
592 	next = NULL;
593 
594 	do {
595 		/*
596 		 * conn->bufsize != conn->buflen at this point,
597 		 * so the buffer can be NUL-terminated below for
598 		 * the case of len == 0.
599 		 */
600 		len = fetch_read(conn, conn->buf + conn->buflen,
601 		    conn->bufsize - conn->buflen);
602 		if (len == -1)
603 			return (-1);
604 		if (len == 0)
605 			break;
606 		next = memchr(conn->buf + conn->buflen, '\n', (size_t)len);
607 		conn->buflen += len;
608 		if (conn->buflen == conn->bufsize && next == NULL) {
609 			tmp = conn->buf;
610 			tmpsize = conn->bufsize * 2;
611 			if (tmpsize < conn->bufsize) {
612 				errno = ENOMEM;
613 				return (-1);
614 			}
615 			if ((tmp = realloc(tmp, tmpsize)) == NULL) {
616 				errno = ENOMEM;
617 				return (-1);
618 			}
619 			conn->buf = tmp;
620 			conn->bufsize = tmpsize;
621 		}
622 	} while (next == NULL);
623 
624 	if (next != NULL) {
625 		*next = '\0';
626 		conn->next_buf = next + 1;
627 		conn->next_len = conn->buflen - (conn->next_buf - conn->buf);
628 		conn->buflen = next - conn->buf;
629 	} else {
630 		conn->buf[conn->buflen] = '\0';
631 		conn->next_len = 0;
632 	}
633 	return (0);
634 }
635 
636 /*
637  * Write a vector to a connection w/ timeout
638  * Note: can modify the iovec.
639  */
640 ssize_t
641 fetch_write(conn_t *conn, const void *buf, size_t len)
642 {
643 	struct timeval now, timeout, waittv;
644 	fd_set writefds;
645 	ssize_t wlen, total;
646 	int r;
647 #ifndef MSG_NOSIGNAL
648 	static int killed_sigpipe;
649 #endif
650 
651 #ifndef MSG_NOSIGNAL
652 	if (!killed_sigpipe) {
653 		signal(SIGPIPE, SIG_IGN);
654 		killed_sigpipe = 1;
655 	}
656 #endif
657 
658 
659 	if (fetchTimeout) {
660 		FD_ZERO(&writefds);
661 		gettimeofday(&timeout, NULL);
662 		timeout.tv_sec += fetchTimeout;
663 	}
664 
665 	total = 0;
666 	while (len) {
667 		while (fetchTimeout && !FD_ISSET(conn->sd, &writefds)) {
668 			FD_SET(conn->sd, &writefds);
669 			gettimeofday(&now, NULL);
670 			waittv.tv_sec = timeout.tv_sec - now.tv_sec;
671 			waittv.tv_usec = timeout.tv_usec - now.tv_usec;
672 			if (waittv.tv_usec < 0) {
673 				waittv.tv_usec += 1000000;
674 				waittv.tv_sec--;
675 			}
676 			if (waittv.tv_sec < 0) {
677 				errno = ETIMEDOUT;
678 				fetch_syserr();
679 				return (-1);
680 			}
681 			errno = 0;
682 			r = select(conn->sd + 1, NULL, &writefds, NULL, &waittv);
683 			if (r == -1) {
684 				if (errno == EINTR && fetchRestartCalls)
685 					continue;
686 				return (-1);
687 			}
688 		}
689 		errno = 0;
690 #ifdef WITH_SSL
691 		if (conn->ssl != NULL)
692 			wlen = SSL_write(conn->ssl, buf, (int)len);
693 		else
694 #endif
695 #ifndef MSG_NOSIGNAL
696 			wlen = send(conn->sd, buf, len, 0);
697 #else
698 			wlen = send(conn->sd, buf, len, MSG_NOSIGNAL);
699 #endif
700 		if (wlen == 0) {
701 			/* we consider a short write a failure */
702 			errno = EPIPE;
703 			fetch_syserr();
704 			return (-1);
705 		}
706 		if (wlen < 0) {
707 			if (errno == EINTR && fetchRestartCalls)
708 				continue;
709 			return (-1);
710 		}
711 		total += wlen;
712 		buf = (const char *)buf + wlen;
713 		len -= wlen;
714 	}
715 	return (total);
716 }
717 
718 
719 /*
720  * Close connection
721  */
722 int
723 fetch_close(conn_t *conn)
724 {
725 	int ret;
726 
727 #ifdef WITH_SSL
728 	if (conn->ssl) {
729 		SSL_shutdown(conn->ssl);
730 		SSL_set_connect_state(conn->ssl);
731 		SSL_free(conn->ssl);
732 		conn->ssl = NULL;
733 	}
734 	if (conn->ssl_ctx) {
735 		SSL_CTX_free(conn->ssl_ctx);
736 		conn->ssl_ctx = NULL;
737 	}
738 	if (conn->ssl_cert) {
739 		X509_free(conn->ssl_cert);
740 		conn->ssl_cert = NULL;
741 	}
742 #endif
743 	ret = close(conn->sd);
744 	if (conn->cache_url)
745 		fetchFreeURL(conn->cache_url);
746 	free(conn->ftp_home);
747 	free(conn->buf);
748 	free(conn);
749 	return (ret);
750 }
751 
752 
753 /*** Directory-related utility functions *************************************/
754 
755 int
756 fetch_add_entry(struct url_list *ue, struct url *base, const char *name,
757     int pre_quoted)
758 {
759 	struct url *tmp;
760 	char *tmp_name;
761 	size_t base_doc_len, name_len, i;
762 	unsigned char c;
763 
764 	if (strchr(name, '/') != NULL ||
765 	    strcmp(name, "..") == 0 ||
766 	    strcmp(name, ".") == 0)
767 		return 0;
768 
769 	if (strcmp(base->doc, "/") == 0)
770 		base_doc_len = 0;
771 	else
772 		base_doc_len = strlen(base->doc);
773 
774 	name_len = 1;
775 	for (i = 0; name[i] != '\0'; ++i) {
776 		if ((!pre_quoted && name[i] == '%') ||
777 		    !fetch_urlpath_safe(name[i]))
778 			name_len += 3;
779 		else
780 			++name_len;
781 	}
782 
783 	tmp_name = malloc( base_doc_len + name_len + 1);
784 	if (tmp_name == NULL) {
785 		errno = ENOMEM;
786 		fetch_syserr();
787 		return (-1);
788 	}
789 
790 	if (ue->length + 1 >= ue->alloc_size) {
791 		tmp = realloc(ue->urls, (ue->alloc_size * 2 + 1) * sizeof(*tmp));
792 		if (tmp == NULL) {
793 			free(tmp_name);
794 			errno = ENOMEM;
795 			fetch_syserr();
796 			return (-1);
797 		}
798 		ue->alloc_size = ue->alloc_size * 2 + 1;
799 		ue->urls = tmp;
800 	}
801 
802 	tmp = ue->urls + ue->length;
803 	strcpy(tmp->scheme, base->scheme);
804 	strcpy(tmp->user, base->user);
805 	strcpy(tmp->pwd, base->pwd);
806 	strcpy(tmp->host, base->host);
807 	tmp->port = base->port;
808 	tmp->doc = tmp_name;
809 	memcpy(tmp->doc, base->doc, base_doc_len);
810 	tmp->doc[base_doc_len] = '/';
811 
812 	for (i = base_doc_len + 1; *name != '\0'; ++name) {
813 		if ((!pre_quoted && *name == '%') ||
814 		    !fetch_urlpath_safe(*name)) {
815 			tmp->doc[i++] = '%';
816 			c = (unsigned char)*name / 16;
817 			if (c < 10)
818 				tmp->doc[i++] = '0' + c;
819 			else
820 				tmp->doc[i++] = 'a' - 10 + c;
821 			c = (unsigned char)*name % 16;
822 			if (c < 10)
823 				tmp->doc[i++] = '0' + c;
824 			else
825 				tmp->doc[i++] = 'a' - 10 + c;
826 		} else {
827 			tmp->doc[i++] = *name;
828 		}
829 	}
830 	tmp->doc[i] = '\0';
831 
832 	tmp->offset = 0;
833 	tmp->length = 0;
834 	tmp->last_modified = -1;
835 
836 	++ue->length;
837 
838 	return (0);
839 }
840 
841 void
842 fetchInitURLList(struct url_list *ue)
843 {
844 	ue->length = ue->alloc_size = 0;
845 	ue->urls = NULL;
846 }
847 
848 int
849 fetchAppendURLList(struct url_list *dst, const struct url_list *src)
850 {
851 	size_t i, j, len;
852 
853 	len = dst->length + src->length;
854 	if (len > dst->alloc_size) {
855 		struct url *tmp;
856 
857 		tmp = realloc(dst->urls, len * sizeof(*tmp));
858 		if (tmp == NULL) {
859 			errno = ENOMEM;
860 			fetch_syserr();
861 			return (-1);
862 		}
863 		dst->alloc_size = len;
864 		dst->urls = tmp;
865 	}
866 
867 	for (i = 0, j = dst->length; i < src->length; ++i, ++j) {
868 		dst->urls[j] = src->urls[i];
869 		dst->urls[j].doc = strdup(src->urls[i].doc);
870 		if (dst->urls[j].doc == NULL) {
871 			while (i-- > 0)
872 				free(dst->urls[j].doc);
873 			fetch_syserr();
874 			return -1;
875 		}
876 	}
877 	dst->length = len;
878 
879 	return 0;
880 }
881 
882 void
883 fetchFreeURLList(struct url_list *ue)
884 {
885 	size_t i;
886 
887 	for (i = 0; i < ue->length; ++i)
888 		free(ue->urls[i].doc);
889 	free(ue->urls);
890 	ue->length = ue->alloc_size = 0;
891 }
892 
893 
894 /*** Authentication-related utility functions ********************************/
895 
896 static const char *
897 fetch_read_word(FILE *f)
898 {
899 	static char word[1024];
900 
901 	if (fscanf(f, " %1023s ", word) != 1)
902 		return (NULL);
903 	return (word);
904 }
905 
906 /*
907  * Get authentication data for a URL from .netrc
908  */
909 int
910 fetch_netrc_auth(struct url *url)
911 {
912 	char fn[PATH_MAX];
913 	const char *word;
914 	char *p;
915 	FILE *f;
916 
917 	if ((p = getenv("NETRC")) != NULL) {
918 		if (snprintf(fn, sizeof(fn), "%s", p) >= (int)sizeof(fn)) {
919 			fetch_info("$NETRC specifies a file name "
920 			    "longer than PATH_MAX");
921 			return (-1);
922 		}
923 	} else {
924 		if ((p = getenv("HOME")) != NULL) {
925 			struct passwd *pwd;
926 
927 			if ((pwd = getpwuid(getuid())) == NULL ||
928 			    (p = pwd->pw_dir) == NULL)
929 				return (-1);
930 		}
931 		if (snprintf(fn, sizeof(fn), "%s/.netrc", p) >= (int)sizeof(fn))
932 			return (-1);
933 	}
934 
935 	if ((f = fopen(fn, "r")) == NULL)
936 		return (-1);
937 	while ((word = fetch_read_word(f)) != NULL) {
938 		if (strcmp(word, "default") == 0)
939 			break;
940 		if (strcmp(word, "machine") == 0 &&
941 		    (word = fetch_read_word(f)) != NULL &&
942 		    strcasecmp(word, url->host) == 0) {
943 			break;
944 		}
945 	}
946 	if (word == NULL)
947 		goto ferr;
948 	while ((word = fetch_read_word(f)) != NULL) {
949 		if (strcmp(word, "login") == 0) {
950 			if ((word = fetch_read_word(f)) == NULL)
951 				goto ferr;
952 			if (snprintf(url->user, sizeof(url->user),
953 				"%s", word) > (int)sizeof(url->user)) {
954 				fetch_info("login name in .netrc is too long");
955 				url->user[0] = '\0';
956 			}
957 		} else if (strcmp(word, "password") == 0) {
958 			if ((word = fetch_read_word(f)) == NULL)
959 				goto ferr;
960 			if (snprintf(url->pwd, sizeof(url->pwd),
961 				"%s", word) > (int)sizeof(url->pwd)) {
962 				fetch_info("password in .netrc is too long");
963 				url->pwd[0] = '\0';
964 			}
965 		} else if (strcmp(word, "account") == 0) {
966 			if ((word = fetch_read_word(f)) == NULL)
967 				goto ferr;
968 			/* XXX not supported! */
969 		} else {
970 			break;
971 		}
972 	}
973 	fclose(f);
974 	return (0);
975  ferr:
976 	fclose(f);
977 	return (-1);
978 }
979 
980 /*
981  * The no_proxy environment variable specifies a set of domains for
982  * which the proxy should not be consulted; the contents is a comma-,
983  * or space-separated list of domain names.  A single asterisk will
984  * override all proxy variables and no transactions will be proxied
985  * (for compatability with lynx and curl, see the discussion at
986  * <http://curl.haxx.se/mail/archive_pre_oct_99/0009.html>).
987  */
988 int
989 fetch_no_proxy_match(const char *host)
990 {
991 	const char *no_proxy, *p, *q;
992 	size_t h_len, d_len;
993 
994 	if ((no_proxy = getenv("NO_PROXY")) == NULL &&
995 	    (no_proxy = getenv("no_proxy")) == NULL)
996 		return (0);
997 
998 	/* asterisk matches any hostname */
999 	if (strcmp(no_proxy, "*") == 0)
1000 		return (1);
1001 
1002 	h_len = strlen(host);
1003 	p = no_proxy;
1004 	do {
1005 		/* position p at the beginning of a domain suffix */
1006 		while (*p == ',' || isspace((unsigned char)*p))
1007 			p++;
1008 
1009 		/* position q at the first separator character */
1010 		for (q = p; *q; ++q)
1011 			if (*q == ',' || isspace((unsigned char)*q))
1012 				break;
1013 
1014 		d_len = q - p;
1015 		if (d_len > 0 && h_len > d_len &&
1016 		    strncasecmp(host + h_len - d_len,
1017 			p, d_len) == 0) {
1018 			/* domain name matches */
1019 			return (1);
1020 		}
1021 
1022 		p = q + 1;
1023 	} while (*q);
1024 
1025 	return (0);
1026 }
1027 
1028 struct fetchIO {
1029 	void *io_cookie;
1030 	ssize_t (*io_read)(void *, void *, size_t);
1031 	ssize_t (*io_write)(void *, const void *, size_t);
1032 	void (*io_close)(void *);
1033 };
1034 
1035 void
1036 fetchIO_close(fetchIO *f)
1037 {
1038 	if (f->io_close != NULL)
1039 		(*f->io_close)(f->io_cookie);
1040 
1041 	free(f);
1042 }
1043 
1044 fetchIO *
1045 fetchIO_unopen(void *io_cookie, ssize_t (*io_read)(void *, void *, size_t),
1046     ssize_t (*io_write)(void *, const void *, size_t),
1047     void (*io_close)(void *))
1048 {
1049 	fetchIO *f;
1050 
1051 	f = malloc(sizeof(*f));
1052 	if (f == NULL)
1053 		return f;
1054 
1055 	f->io_cookie = io_cookie;
1056 	f->io_read = io_read;
1057 	f->io_write = io_write;
1058 	f->io_close = io_close;
1059 
1060 	return f;
1061 }
1062 
1063 ssize_t
1064 fetchIO_read(fetchIO *f, void *buf, size_t len)
1065 {
1066 	if (f->io_read == NULL)
1067 		return EBADF;
1068 	return (*f->io_read)(f->io_cookie, buf, len);
1069 }
1070 
1071 ssize_t
1072 fetchIO_write(fetchIO *f, const void *buf, size_t len)
1073 {
1074 	if (f->io_read == NULL)
1075 		return EBADF;
1076 	return (*f->io_write)(f->io_cookie, buf, len);
1077 }
1078