common.c revision 77237
1/*-
2 * Copyright (c) 1998 Dag-Erling Co�dan Sm�rgrav
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer
10 *    in this position and unchanged.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 *
28 * $FreeBSD: head/lib/libfetch/common.c 77237 2001-05-26 19:36:49Z des $
29 */
30
31#include <sys/param.h>
32#include <sys/socket.h>
33#include <sys/time.h>
34#include <sys/uio.h>
35#include <netinet/in.h>
36
37#include <errno.h>
38#include <netdb.h>
39#include <stdarg.h>
40#include <stdlib.h>
41#include <stdio.h>
42#include <string.h>
43#include <unistd.h>
44
45#include "fetch.h"
46#include "common.h"
47
48
49/*** Local data **************************************************************/
50
51/*
52 * Error messages for resolver errors
53 */
54static struct fetcherr _netdb_errlist[] = {
55    { EAI_NODATA,	FETCH_RESOLV,	"Host not found" },
56    { EAI_AGAIN,	FETCH_TEMP,	"Transient resolver failure" },
57    { EAI_FAIL,		FETCH_RESOLV,	"Non-recoverable resolver failure" },
58    { EAI_NONAME,	FETCH_RESOLV,	"No address record" },
59    { -1,		FETCH_UNKNOWN,	"Unknown resolver error" }
60};
61
62/* End-of-Line */
63static const char ENDL[2] = "\r\n";
64
65
66/*** Error-reporting functions ***********************************************/
67
68/*
69 * Map error code to string
70 */
71static struct fetcherr *
72_fetch_finderr(struct fetcherr *p, int e)
73{
74    while (p->num != -1 && p->num != e)
75	p++;
76    return p;
77}
78
79/*
80 * Set error code
81 */
82void
83_fetch_seterr(struct fetcherr *p, int e)
84{
85    p = _fetch_finderr(p, e);
86    fetchLastErrCode = p->cat;
87    snprintf(fetchLastErrString, MAXERRSTRING, "%s", p->string);
88}
89
90/*
91 * Set error code according to errno
92 */
93void
94_fetch_syserr(void)
95{
96    int e;
97    e = errno;
98
99    switch (errno) {
100    case 0:
101	fetchLastErrCode = FETCH_OK;
102	break;
103    case EPERM:
104    case EACCES:
105    case EROFS:
106    case EAUTH:
107    case ENEEDAUTH:
108	fetchLastErrCode = FETCH_AUTH;
109	break;
110    case ENOENT:
111    case EISDIR: /* XXX */
112	fetchLastErrCode = FETCH_UNAVAIL;
113	break;
114    case ENOMEM:
115	fetchLastErrCode = FETCH_MEMORY;
116	break;
117    case EBUSY:
118    case EAGAIN:
119	fetchLastErrCode = FETCH_TEMP;
120	break;
121    case EEXIST:
122	fetchLastErrCode = FETCH_EXISTS;
123	break;
124    case ENOSPC:
125	fetchLastErrCode = FETCH_FULL;
126	break;
127    case EADDRINUSE:
128    case EADDRNOTAVAIL:
129    case ENETDOWN:
130    case ENETUNREACH:
131    case ENETRESET:
132    case EHOSTUNREACH:
133	fetchLastErrCode = FETCH_NETWORK;
134	break;
135    case ECONNABORTED:
136    case ECONNRESET:
137	fetchLastErrCode = FETCH_ABORT;
138	break;
139    case ETIMEDOUT:
140	fetchLastErrCode = FETCH_TIMEOUT;
141	break;
142    case ECONNREFUSED:
143    case EHOSTDOWN:
144	fetchLastErrCode = FETCH_DOWN;
145	break;
146    default:
147	fetchLastErrCode = FETCH_UNKNOWN;
148    }
149    snprintf(fetchLastErrString, MAXERRSTRING, "%s", strerror(e));
150}
151
152
153/*
154 * Emit status message
155 */
156void
157_fetch_info(const char *fmt, ...)
158{
159    va_list ap;
160
161    va_start(ap, fmt);
162    vfprintf(stderr, fmt, ap);
163    va_end(ap);
164    fputc('\n', stderr);
165}
166
167
168/*** Network-related utility functions ***************************************/
169
170/*
171 * Return the default port for a scheme
172 */
173int
174_fetch_default_port(const char *scheme)
175{
176    struct servent *se;
177
178    if ((se = getservbyname(scheme, "tcp")) != NULL)
179	return ntohs(se->s_port);
180    if (strcasecmp(scheme, SCHEME_FTP) == 0)
181	return FTP_DEFAULT_PORT;
182    if (strcasecmp(scheme, SCHEME_HTTP) == 0)
183	return HTTP_DEFAULT_PORT;
184    return 0;
185}
186
187/*
188 * Return the default proxy port for a scheme
189 */
190int
191_fetch_default_proxy_port(const char *scheme)
192{
193    if (strcasecmp(scheme, SCHEME_FTP) == 0)
194	return FTP_DEFAULT_PROXY_PORT;
195    if (strcasecmp(scheme, SCHEME_HTTP) == 0)
196	return HTTP_DEFAULT_PROXY_PORT;
197    return 0;
198}
199
200/*
201 * Establish a TCP connection to the specified port on the specified host.
202 */
203int
204_fetch_connect(const char *host, int port, int af, int verbose)
205{
206    char pbuf[10];
207    struct addrinfo hints, *res, *res0;
208    int sd, err;
209
210    DEBUG(fprintf(stderr, "\033[1m---> %s:%d\033[m\n", host, port));
211
212    if (verbose)
213	_fetch_info("looking up %s", host);
214
215    /* look up host name and set up socket address structure */
216    snprintf(pbuf, sizeof(pbuf), "%d", port);
217    memset(&hints, 0, sizeof(hints));
218    hints.ai_family = af;
219    hints.ai_socktype = SOCK_STREAM;
220    hints.ai_protocol = 0;
221    if ((err = getaddrinfo(host, pbuf, &hints, &res0)) != 0) {
222	_netdb_seterr(err);
223	return -1;
224    }
225
226    if (verbose)
227	_fetch_info("connecting to %s:%d", host, port);
228
229    /* try to connect */
230    for (sd = -1, res = res0; res; res = res->ai_next) {
231	if ((sd = socket(res->ai_family, res->ai_socktype,
232			 res->ai_protocol)) == -1)
233	    continue;
234	if (connect(sd, res->ai_addr, res->ai_addrlen) != -1)
235	    break;
236	close(sd);
237	sd = -1;
238    }
239    freeaddrinfo(res0);
240    if (sd == -1) {
241	_fetch_syserr();
242	return -1;
243    }
244
245    return sd;
246}
247
248
249/*
250 * Read a line of text from a socket w/ timeout
251 */
252#define MIN_BUF_SIZE 1024
253
254int
255_fetch_getln(int fd, char **buf, size_t *size, size_t *len)
256{
257    struct timeval now, timeout, wait;
258    fd_set readfds;
259    int r;
260    char c;
261
262    if (*buf == NULL) {
263	if ((*buf = malloc(MIN_BUF_SIZE)) == NULL) {
264	    errno = ENOMEM;
265	    return -1;
266	}
267	*size = MIN_BUF_SIZE;
268    }
269
270    **buf = '\0';
271    *len = 0;
272
273    if (fetchTimeout) {
274	gettimeofday(&timeout, NULL);
275	timeout.tv_sec += fetchTimeout;
276	FD_ZERO(&readfds);
277    }
278
279    do {
280	if (fetchTimeout) {
281	    FD_SET(fd, &readfds);
282	    gettimeofday(&now, NULL);
283	    wait.tv_sec = timeout.tv_sec - now.tv_sec;
284	    wait.tv_usec = timeout.tv_usec - now.tv_usec;
285	    if (wait.tv_usec < 0) {
286		wait.tv_usec += 1000000;
287		wait.tv_sec--;
288	    }
289	    if (wait.tv_sec < 0) {
290		errno = ETIMEDOUT;
291		return -1;
292	    }
293	    r = select(fd+1, &readfds, NULL, NULL, &wait);
294	    if (r == -1) {
295		if (errno == EINTR && fetchRestartCalls)
296		    continue;
297		/* EBADF or EINVAL: shouldn't happen */
298		return -1;
299	    }
300	    if (!FD_ISSET(fd, &readfds))
301		continue;
302	}
303	r = read(fd, &c, 1);
304	if (r == 0)
305	    break;
306	if (r == -1) {
307	    if (errno == EINTR && fetchRestartCalls)
308		continue;
309	    /* any other error is bad news */
310	    return -1;
311	}
312	(*buf)[*len] = c;
313	*len += 1;
314	if (*len == *size) {
315	    char *tmp;
316
317	    if ((tmp = realloc(*buf, *size * 2 + 1)) == NULL) {
318		errno = ENOMEM;
319		return -1;
320	    }
321	    *buf = tmp;
322	    *size = *size * 2 + 1;
323	}
324    } while (c != '\n');
325
326    DEBUG(fprintf(stderr, "\033[1m<<< %.*s\033[m", (int)*len, *buf));
327    return 0;
328}
329
330
331/*
332 * Write a line of text to a socket w/ timeout
333 * XXX currently does not enforce timeout
334 */
335int
336_fetch_putln(int fd, const char *str, size_t len)
337{
338    struct iovec iov[2];
339    ssize_t wlen;
340
341    /* XXX should enforce timeout */
342    iov[0].iov_base = (char *)str;
343    iov[0].iov_len = len;
344    iov[1].iov_base = (char *)ENDL;
345    iov[1].iov_len = sizeof ENDL;
346    wlen = writev(fd, iov, 2);
347    DEBUG(fprintf(stderr, "\033[1m>>> %s\n\033[m", str));
348    return (wlen != len);
349}
350
351
352/*** Directory-related utility functions *************************************/
353
354int
355_fetch_add_entry(struct url_ent **p, int *size, int *len,
356		 const char *name, struct url_stat *stat)
357{
358    struct url_ent *tmp;
359
360    if (*p == NULL) {
361#define INITIAL_SIZE 8
362	if ((*p = malloc(INITIAL_SIZE * sizeof **p)) == NULL) {
363	    errno = ENOMEM;
364	    _fetch_syserr();
365	    return -1;
366	}
367	*size = INITIAL_SIZE;
368	*len = 0;
369#undef INITIAL_SIZE
370    }
371
372    if (*len >= *size - 1) {
373	tmp = realloc(*p, *size * 2 * sizeof **p);
374	if (tmp == NULL) {
375	    errno = ENOMEM;
376	    _fetch_syserr();
377	    return -1;
378	}
379	*size *= 2;
380	*p = tmp;
381    }
382
383    tmp = *p + *len;
384    snprintf(tmp->name, PATH_MAX, "%s", name);
385    bcopy(stat, &tmp->stat, sizeof *stat);
386
387    (*len)++;
388    (++tmp)->name[0] = 0;
389
390    return 0;
391}
392