xref: /freebsd/libexec/phttpget/phttpget.c (revision 2008043f386721d58158e37e0d7e50df8095942d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright 2005 Colin Percival
5  * All rights reserved
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted providing 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  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
25  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 #include <sys/types.h>
31 #include <sys/time.h>
32 #include <sys/socket.h>
33 
34 #include <ctype.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <limits.h>
39 #include <netdb.h>
40 #include <stdint.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sysexits.h>
45 #include <unistd.h>
46 
47 static const char *	env_HTTP_PROXY;
48 static char *		env_HTTP_PROXY_AUTH;
49 static const char *	env_HTTP_USER_AGENT;
50 static char *		env_HTTP_TIMEOUT;
51 static const char *	proxyport;
52 static char *		proxyauth;
53 
54 static struct timeval	timo = { 15, 0};
55 
56 static void
57 usage(void)
58 {
59 
60 	fprintf(stderr, "usage: phttpget server [file ...]\n");
61 	exit(EX_USAGE);
62 }
63 
64 /*
65  * Base64 encode a string; the string returned, if non-NULL, is
66  * allocated using malloc() and must be freed by the caller.
67  */
68 static char *
69 b64enc(const char *ptext)
70 {
71 	static const char base64[] =
72 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
73 	    "abcdefghijklmnopqrstuvwxyz"
74 	    "0123456789+/";
75 	const char *pt;
76 	char *ctext, *pc;
77 	size_t ptlen, ctlen;
78 	uint32_t t;
79 	unsigned int j;
80 
81 	/*
82 	 * Encoded length is 4 characters per 3-byte block or partial
83 	 * block of plaintext, plus one byte for the terminating NUL
84 	 */
85 	ptlen = strlen(ptext);
86 	if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
87 		return NULL;	/* Possible integer overflow */
88 	ctlen = 4 * ((ptlen + 2) / 3) + 1;
89 	if ((ctext = malloc(ctlen)) == NULL)
90 		return NULL;
91 	ctext[ctlen - 1] = 0;
92 
93 	/*
94 	 * Scan through ptext, reading up to 3 bytes from ptext and
95 	 * writing 4 bytes to ctext, until we run out of input.
96 	 */
97 	for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
98 		/* Read 3 bytes */
99 		for (t = j = 0; j < 3; j++) {
100 			t <<= 8;
101 			if (j < ptlen)
102 				t += *pt++;
103 		}
104 
105 		/* Write 4 bytes */
106 		for (j = 0; j < 4; j++) {
107 			if (j <= ptlen + 1)
108 				pc[j] = base64[(t >> 18) & 0x3f];
109 			else
110 				pc[j] = '=';
111 			t <<= 6;
112 		}
113 
114 		/* If we're done, exit the loop */
115 		if (ptlen <= 3)
116 			break;
117 	}
118 
119 	return (ctext);
120 }
121 
122 static void
123 readenv(void)
124 {
125 	char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
126 	char *proxy_auth_user = NULL;
127 	char *proxy_auth_pass = NULL;
128 	long http_timeout;
129 
130 	env_HTTP_PROXY = getenv("HTTP_PROXY");
131 	if (env_HTTP_PROXY == NULL)
132 		env_HTTP_PROXY = getenv("http_proxy");
133 	if (env_HTTP_PROXY != NULL) {
134 		if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
135 			env_HTTP_PROXY += 7;
136 		p = strchr(env_HTTP_PROXY, '/');
137 		if (p != NULL)
138 			*p = 0;
139 		p = strchr(env_HTTP_PROXY, ':');
140 		if (p != NULL) {
141 			*p = 0;
142 			proxyport = p + 1;
143 		} else
144 			proxyport = "3128";
145 	}
146 
147 	env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
148 	if ((env_HTTP_PROXY != NULL) &&
149 	    (env_HTTP_PROXY_AUTH != NULL) &&
150 	    (strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
151 		/* Ignore authentication scheme */
152 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
153 
154 		/* Ignore realm */
155 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
156 
157 		/* Obtain username and password */
158 		proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
159 		proxy_auth_pass = env_HTTP_PROXY_AUTH;
160 	}
161 
162 	if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
163 		asprintf(&proxy_auth_userpass, "%s:%s",
164 		    proxy_auth_user, proxy_auth_pass);
165 		if (proxy_auth_userpass == NULL)
166 			err(1, "asprintf");
167 
168 		proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
169 		if (proxy_auth_userpass64 == NULL)
170 			err(1, "malloc");
171 
172 		asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
173 		    proxy_auth_userpass64);
174 		if (proxyauth == NULL)
175 			err(1, "asprintf");
176 
177 		free(proxy_auth_userpass);
178 		free(proxy_auth_userpass64);
179 	} else
180 		proxyauth = NULL;
181 
182 	env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
183 	if (env_HTTP_USER_AGENT == NULL)
184 		env_HTTP_USER_AGENT = "phttpget/0.1";
185 
186 	env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
187 	if (env_HTTP_TIMEOUT != NULL) {
188 		http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
189 		if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
190 		    (http_timeout < 0))
191 			warnx("HTTP_TIMEOUT (%s) is not a positive integer",
192 			    env_HTTP_TIMEOUT);
193 		else
194 			timo.tv_sec = http_timeout;
195 	}
196 }
197 
198 static int
199 makerequest(char ** buf, char * path, char * server, int connclose)
200 {
201 	int buflen;
202 
203 	buflen = asprintf(buf,
204 	    "GET %s%s/%s HTTP/1.1\r\n"
205 	    "Host: %s\r\n"
206 	    "User-Agent: %s\r\n"
207 	    "%s"
208 	    "%s"
209 	    "\r\n",
210 	    env_HTTP_PROXY ? "http://" : "",
211 	    env_HTTP_PROXY ? server : "",
212 	    path, server, env_HTTP_USER_AGENT,
213 	    proxyauth ? proxyauth : "",
214 	    connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
215 	if (buflen == -1)
216 		err(1, "asprintf");
217 	return(buflen);
218 }
219 
220 static int
221 readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
222 {
223 	ssize_t len;
224 
225 	while (strnstr(resbuf + *resbufpos, "\r\n",
226 	    *resbuflen - *resbufpos) == NULL) {
227 		/* Move buffered data to the start of the buffer */
228 		if (*resbufpos != 0) {
229 			memmove(resbuf, resbuf + *resbufpos,
230 			    *resbuflen - *resbufpos);
231 			*resbuflen -= *resbufpos;
232 			*resbufpos = 0;
233 		}
234 
235 		/* If the buffer is full, complain */
236 		if (*resbuflen == BUFSIZ)
237 			return -1;
238 
239 		/* Read more data into the buffer */
240 		len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
241 		if ((len == 0) ||
242 		    ((len == -1) && (errno != EINTR)))
243 			return -1;
244 
245 		if (len != -1)
246 			*resbuflen += len;
247 	}
248 
249 	return 0;
250 }
251 
252 static int
253 copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
254     int * resbufpos)
255 {
256 	ssize_t len;
257 
258 	while (copylen) {
259 		/* Write data from resbuf to fd */
260 		len = *resbuflen - *resbufpos;
261 		if (copylen < len)
262 			len = copylen;
263 		if (len > 0) {
264 			if (fd != -1)
265 				len = write(fd, resbuf + *resbufpos, len);
266 			if (len == -1)
267 				err(1, "write");
268 			*resbufpos += len;
269 			copylen -= len;
270 			continue;
271 		}
272 
273 		/* Read more data into buffer */
274 		len = recv(sd, resbuf, BUFSIZ, 0);
275 		if (len == -1) {
276 			if (errno == EINTR)
277 				continue;
278 			return -1;
279 		} else if (len == 0) {
280 			return -2;
281 		} else {
282 			*resbuflen = len;
283 			*resbufpos = 0;
284 		}
285 	}
286 
287 	return 0;
288 }
289 
290 int
291 main(int argc, char *argv[])
292 {
293 	struct addrinfo hints;	/* Hints to getaddrinfo */
294 	struct addrinfo *res;	/* Pointer to server address being used */
295 	struct addrinfo *res0;	/* Pointer to server addresses */
296 	char * resbuf = NULL;	/* Response buffer */
297 	int resbufpos = 0;	/* Response buffer position */
298 	int resbuflen = 0;	/* Response buffer length */
299 	char * eolp;		/* Pointer to "\r\n" within resbuf */
300 	char * hln;		/* Pointer within header line */
301 	char * servername;	/* Name of server */
302 	char * fname = NULL;	/* Name of downloaded file */
303 	char * reqbuf = NULL;	/* Request buffer */
304 	int reqbufpos = 0;	/* Request buffer position */
305 	int reqbuflen = 0;	/* Request buffer length */
306 	ssize_t len;		/* Length sent or received */
307 	int nreq = 0;		/* Number of next request to send */
308 	int nres = 0;		/* Number of next reply to receive */
309 	int pipelined = 0;	/* != 0 if connection in pipelined mode. */
310 	int keepalive;		/* != 0 if HTTP/1.0 keep-alive rcvd. */
311 	int sd = -1;		/* Socket descriptor */
312 	int sdflags = 0;	/* Flags on the socket sd */
313 	int fd = -1;		/* Descriptor for download target file */
314 	int error;		/* Error code */
315 	int statuscode;		/* HTTP Status code */
316 	off_t contentlength;	/* Value from Content-Length header */
317 	int chunked;		/* != if transfer-encoding is chunked */
318 	off_t clen;		/* Chunk length */
319 	int firstreq = 0;	/* # of first request for this connection */
320 	int val;		/* Value used for setsockopt call */
321 
322 	/* Check that the arguments are sensible */
323 	if (argc < 2)
324 		usage();
325 
326 	/* Read important environment variables */
327 	readenv();
328 
329 	/* Get server name and adjust arg[cv] to point at file names */
330 	servername = argv[1];
331 	argv += 2;
332 	argc -= 2;
333 
334 	/* Allocate response buffer */
335 	resbuf = malloc(BUFSIZ);
336 	if (resbuf == NULL)
337 		err(1, "malloc");
338 
339 	/* Look up server */
340 	memset(&hints, 0, sizeof(hints));
341 	hints.ai_family = PF_UNSPEC;
342 	hints.ai_socktype = SOCK_STREAM;
343 	error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
344 	    env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
345 	if (error)
346 		errx(1, "host = %s, port = %s: %s",
347 		    env_HTTP_PROXY ? env_HTTP_PROXY : servername,
348 		    env_HTTP_PROXY ? proxyport : "http",
349 		    gai_strerror(error));
350 	if (res0 == NULL)
351 		errx(1, "could not look up %s", servername);
352 	res = res0;
353 
354 	/* Do the fetching */
355 	while (nres < argc) {
356 		/* Make sure we have a connected socket */
357 		for (; sd == -1; res = res->ai_next) {
358 			/* No addresses left to try :-( */
359 			if (res == NULL)
360 				errx(1, "Could not connect to %s", servername);
361 
362 			/* Create a socket... */
363 			sd = socket(res->ai_family, res->ai_socktype,
364 			    res->ai_protocol);
365 			if (sd == -1)
366 				continue;
367 
368 			/* ... set 15-second timeouts ... */
369 			setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
370 			    (void *)&timo, (socklen_t)sizeof(timo));
371 			setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
372 			    (void *)&timo, (socklen_t)sizeof(timo));
373 
374 			/* ... disable SIGPIPE generation ... */
375 			val = 1;
376 			setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
377 			    (void *)&val, sizeof(int));
378 
379 			/* ... and connect to the server. */
380 			if(connect(sd, res->ai_addr, res->ai_addrlen)) {
381 				close(sd);
382 				sd = -1;
383 				continue;
384 			}
385 
386 			firstreq = nres;
387 		}
388 
389 		/*
390 		 * If in pipelined HTTP mode, put socket into non-blocking
391 		 * mode, since we're probably going to want to try to send
392 		 * several HTTP requests.
393 		 */
394 		if (pipelined) {
395 			sdflags = fcntl(sd, F_GETFL);
396 			if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
397 				err(1, "fcntl");
398 		}
399 
400 		/* Construct requests and/or send them without blocking */
401 		while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
402 			/* If not in the middle of a request, make one */
403 			if (reqbuf == NULL) {
404 				reqbuflen = makerequest(&reqbuf, argv[nreq],
405 				    servername, (nreq == argc - 1));
406 				reqbufpos = 0;
407 			}
408 
409 			/* If in pipelined mode, try to send the request */
410 			if (pipelined) {
411 				while (reqbufpos < reqbuflen) {
412 					len = send(sd, reqbuf + reqbufpos,
413 					    reqbuflen - reqbufpos, 0);
414 					if (len == -1)
415 						break;
416 					reqbufpos += len;
417 				}
418 				if (reqbufpos < reqbuflen) {
419 					if (errno != EAGAIN)
420 						goto conndied;
421 					break;
422 				} else {
423 					free(reqbuf);
424 					reqbuf = NULL;
425 					nreq++;
426 				}
427 			}
428 		}
429 
430 		/* Put connection back into blocking mode */
431 		if (pipelined) {
432 			if (fcntl(sd, F_SETFL, sdflags) == -1)
433 				err(1, "fcntl");
434 		}
435 
436 		/* Do we need to blocking-send a request? */
437 		if (nres == nreq) {
438 			while (reqbufpos < reqbuflen) {
439 				len = send(sd, reqbuf + reqbufpos,
440 				    reqbuflen - reqbufpos, 0);
441 				if (len == -1)
442 					goto conndied;
443 				reqbufpos += len;
444 			}
445 			free(reqbuf);
446 			reqbuf = NULL;
447 			nreq++;
448 		}
449 
450 		/* Scan through the response processing headers. */
451 		statuscode = 0;
452 		contentlength = -1;
453 		chunked = 0;
454 		keepalive = 0;
455 		do {
456 			/* Get a header line */
457 			error = readln(sd, resbuf, &resbuflen, &resbufpos);
458 			if (error)
459 				goto conndied;
460 			hln = resbuf + resbufpos;
461 			eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
462 			resbufpos = (eolp - resbuf) + 2;
463 			*eolp = '\0';
464 
465 			/* Make sure it doesn't contain a NUL character */
466 			if (strchr(hln, '\0') != eolp)
467 				goto conndied;
468 
469 			if (statuscode == 0) {
470 				/* The first line MUST be HTTP/1.x xxx ... */
471 				if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
472 				    ! isdigit(hln[7]))
473 					goto conndied;
474 
475 				/*
476 				 * If the minor version number isn't zero,
477 				 * then we can assume that pipelining our
478 				 * requests is OK -- as long as we don't
479 				 * see a "Connection: close" line later
480 				 * and we either have a Content-Length or
481 				 * Transfer-Encoding: chunked header to
482 				 * tell us the length.
483 				 */
484 				if (hln[7] != '0')
485 					pipelined = 1;
486 
487 				/* Skip over the minor version number */
488 				hln = strchr(hln + 7, ' ');
489 				if (hln == NULL)
490 					goto conndied;
491 				else
492 					hln++;
493 
494 				/* Read the status code */
495 				while (isdigit(*hln)) {
496 					statuscode = statuscode * 10 +
497 					    *hln - '0';
498 					hln++;
499 				}
500 
501 				if (statuscode < 100 || statuscode > 599)
502 					goto conndied;
503 
504 				/* Ignore the rest of the line */
505 				continue;
506 			}
507 
508 			/*
509 			 * Check for "Connection: close" or
510 			 * "Connection: Keep-Alive" header
511 			 */
512 			if (strncasecmp(hln, "Connection:", 11) == 0) {
513 				hln += 11;
514 				if (strcasestr(hln, "close") != NULL)
515 					pipelined = 0;
516 				if (strcasestr(hln, "Keep-Alive") != NULL)
517 					keepalive = 1;
518 
519 				/* Next header... */
520 				continue;
521 			}
522 
523 			/* Check for "Content-Length:" header */
524 			if (strncasecmp(hln, "Content-Length:", 15) == 0) {
525 				hln += 15;
526 				contentlength = 0;
527 
528 				/* Find the start of the length */
529 				while (!isdigit(*hln) && (*hln != '\0'))
530 					hln++;
531 
532 				/* Compute the length */
533 				while (isdigit(*hln)) {
534 					if (contentlength >= OFF_MAX / 10) {
535 						/* Nasty people... */
536 						goto conndied;
537 					}
538 					contentlength = contentlength * 10 +
539 					    *hln - '0';
540 					hln++;
541 				}
542 
543 				/* Next header... */
544 				continue;
545 			}
546 
547 			/* Check for "Transfer-Encoding: chunked" header */
548 			if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) {
549 				hln += 18;
550 				if (strcasestr(hln, "chunked") != NULL)
551 					chunked = 1;
552 
553 				/* Next header... */
554 				continue;
555 			}
556 
557 			/* We blithely ignore any other header lines */
558 
559 			/* No more header lines */
560 			if (strlen(hln) == 0) {
561 				/*
562 				 * If the status code was 1xx, then there will
563 				 * be a real header later.  Servers may emit
564 				 * 1xx header blocks at will, but since we
565 				 * don't expect one, we should just ignore it.
566 				 */
567 				if (100 <= statuscode && statuscode <= 199) {
568 					statuscode = 0;
569 					continue;
570 				}
571 
572 				/* End of header; message body follows */
573 				break;
574 			}
575 		} while (1);
576 
577 		/* No message body for 204 or 304 */
578 		if (statuscode == 204 || statuscode == 304) {
579 			nres++;
580 			continue;
581 		}
582 
583 		/*
584 		 * There should be a message body coming, but we only want
585 		 * to send it to a file if the status code is 200
586 		 */
587 		if (statuscode == 200) {
588 			/* Generate a file name for the download */
589 			fname = strrchr(argv[nres], '/');
590 			if (fname == NULL)
591 				fname = argv[nres];
592 			else
593 				fname++;
594 			if (strlen(fname) == 0)
595 				errx(1, "Cannot obtain file name from %s\n",
596 				    argv[nres]);
597 
598 			fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
599 			if (fd == -1)
600 				errx(1, "open(%s)", fname);
601 		}
602 
603 		/* Read the message and send data to fd if appropriate */
604 		if (chunked) {
605 			/* Handle a chunked-encoded entity */
606 
607 			/* Read chunks */
608 			do {
609 				error = readln(sd, resbuf, &resbuflen,
610 				    &resbufpos);
611 				if (error)
612 					goto conndied;
613 				hln = resbuf + resbufpos;
614 				eolp = strstr(hln, "\r\n");
615 				resbufpos = (eolp - resbuf) + 2;
616 
617 				clen = 0;
618 				while (isxdigit(*hln)) {
619 					if (clen >= OFF_MAX / 16) {
620 						/* Nasty people... */
621 						goto conndied;
622 					}
623 					if (isdigit(*hln))
624 						clen = clen * 16 + *hln - '0';
625 					else
626 						clen = clen * 16 + 10 +
627 						    tolower(*hln) - 'a';
628 					hln++;
629 				}
630 
631 				error = copybytes(sd, fd, clen, resbuf,
632 				    &resbuflen, &resbufpos);
633 				if (error) {
634 					goto conndied;
635 				}
636 			} while (clen != 0);
637 
638 			/* Read trailer and final CRLF */
639 			do {
640 				error = readln(sd, resbuf, &resbuflen,
641 				    &resbufpos);
642 				if (error)
643 					goto conndied;
644 				hln = resbuf + resbufpos;
645 				eolp = strstr(hln, "\r\n");
646 				resbufpos = (eolp - resbuf) + 2;
647 			} while (hln != eolp);
648 		} else if (contentlength != -1) {
649 			error = copybytes(sd, fd, contentlength, resbuf,
650 			    &resbuflen, &resbufpos);
651 			if (error)
652 				goto conndied;
653 		} else {
654 			/*
655 			 * Not chunked, and no content length header.
656 			 * Read everything until the server closes the
657 			 * socket.
658 			 */
659 			error = copybytes(sd, fd, OFF_MAX, resbuf,
660 			    &resbuflen, &resbufpos);
661 			if (error == -1)
662 				goto conndied;
663 			pipelined = 0;
664 		}
665 
666 		if (fd != -1) {
667 			close(fd);
668 			fd = -1;
669 		}
670 
671 		fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
672 		    statuscode);
673 		if (statuscode == 200)
674 			fprintf(stderr, "OK\n");
675 		else if (statuscode < 300)
676 			fprintf(stderr, "Successful (ignored)\n");
677 		else if (statuscode < 400)
678 			fprintf(stderr, "Redirection (ignored)\n");
679 		else
680 			fprintf(stderr, "Error (ignored)\n");
681 
682 		/* We've finished this file! */
683 		nres++;
684 
685 		/*
686 		 * If necessary, clean up this connection so that we
687 		 * can start a new one.
688 		 */
689 		if (pipelined == 0 && keepalive == 0)
690 			goto cleanupconn;
691 		continue;
692 
693 conndied:
694 		/*
695 		 * Something went wrong -- our connection died, the server
696 		 * sent us garbage, etc.  If this happened on the first
697 		 * request we sent over this connection, give up.  Otherwise,
698 		 * close this connection, open a new one, and reissue the
699 		 * request.
700 		 */
701 		if (nres == firstreq)
702 			errx(1, "Connection failure");
703 
704 cleanupconn:
705 		/*
706 		 * Clean up our connection and keep on going
707 		 */
708 		shutdown(sd, SHUT_RDWR);
709 		close(sd);
710 		sd = -1;
711 		if (fd != -1) {
712 			close(fd);
713 			fd = -1;
714 		}
715 		if (reqbuf != NULL) {
716 			free(reqbuf);
717 			reqbuf = NULL;
718 		}
719 		nreq = nres;
720 		res = res0;
721 		pipelined = 0;
722 		resbufpos = resbuflen = 0;
723 		continue;
724 	}
725 
726 	free(resbuf);
727 	freeaddrinfo(res0);
728 
729 	return 0;
730 }
731