chiark / gitweb /
Actually report "no such host" errors properly
[inn-innduct.git] / lib / remopen.c
1 /*  $Revision: 6119 $
2 **
3 **  Open a connection to a remote NNTP server.
4 */
5
6 #include "config.h"
7 #include "clibrary.h"
8 #include "portable/socket.h"
9 #include <errno.h>
10 #include <netdb.h>
11
12 #ifdef HAVE_UNIX_DOMAIN_SOCKETS
13 # include <sys/un.h>
14 #endif
15
16 #include "inn/innconf.h"
17 #include "libinn.h"
18 #include "nntp.h"
19 #include "paths.h"
20
21 /*
22 ** store an error message in buff like libinn(3) says NNTPconnect does
23 */
24 static void store_error(char *buff, char *string1, char *string2) {
25     /* we put these in [...] to make it easier to tell them apart
26      * from messages sent by the server */
27     snprintf(buff, NNTP_STRLEN, "[%s%s]", string1, string2);
28     buff[NNTP_STRLEN-1] = 0;
29 }
30
31 /*
32 **  Open a connection to an NNTP server and create stdio FILE's for talking
33 **  to it.  Return -1 on error.
34 */
35 int NNTPconnect(char *host, int port, FILE **FromServerp, FILE **ToServerp, char *errbuff)
36 {
37     char                mybuff[NNTP_STRLEN + 2];
38     char                *buff;
39     int                 i = -1;
40     int                 j;
41     int                 oerrno;
42     int                 gai_r;
43     FILE                *F;
44 #ifdef HAVE_INET6
45     struct addrinfo     hints, *ressave, *addr;
46     char                portbuf[16];
47     struct sockaddr_storage client;
48 #else
49     char                **ap;
50     char                *dest;
51     char                *fakelist[2];
52     char                *p;
53     struct hostent      *hp;
54     struct hostent      fakehp;
55     struct in_addr      quadaddr;
56     struct sockaddr_in  server, client;
57 #endif
58
59     buff = errbuff ? errbuff : mybuff;
60     *buff = '\0';
61
62 #ifdef HAVE_INET6
63     memset(&hints, 0, sizeof(struct addrinfo));
64     hints.ai_family = PF_UNSPEC;
65     hints.ai_socktype = SOCK_STREAM;
66     sprintf(portbuf, "%d", port);
67     if ((gai_r = getaddrinfo(host, portbuf, &hints, &addr)) != 0) {
68         store_error(buff, "getaddrinfo failed: ", gai_strerror(gai_r));
69         errno = 0;
70         return -1;
71     }
72
73     for (ressave = addr; addr; addr = addr->ai_next) {
74         if ((i = socket(addr->ai_family, addr->ai_socktype,
75                         addr->ai_protocol)) < 0)
76             continue; /* ignore */
77         /* bind the local (source) address, if requested */
78         memset(&client, 0, sizeof client);
79         if (addr->ai_family == AF_INET && innconf->sourceaddress) {
80             if (inet_pton(AF_INET, innconf->sourceaddress,
81                         &((struct sockaddr_in *)&client)->sin_addr) < 1) {
82                 addr = NULL;
83                 break;
84             }
85             ((struct sockaddr_in *)&client)->sin_family = AF_INET;
86 #ifdef HAVE_SOCKADDR_LEN
87             ((struct sockaddr_in *)&client)->sin_len = sizeof( struct sockaddr_in );
88 #endif
89         }
90         if (addr->ai_family == AF_INET6 && innconf->sourceaddress6) {
91             if (inet_pton(AF_INET6, innconf->sourceaddress6,
92                         &((struct sockaddr_in6 *)&client)->sin6_addr) < 1) {
93                 addr = NULL;
94                 break;
95             }
96             ((struct sockaddr_in6 *)&client)->sin6_family = AF_INET6;
97 #ifdef HAVE_SOCKADDR_LEN
98             ((struct sockaddr_in6 *)&client)->sin6_len = sizeof( struct sockaddr_in6 );
99 #endif
100         }
101         if (client.ss_family != 0) {
102             if (bind(i, (struct sockaddr *)&client, addr->ai_addrlen) < 0) {
103                 addr = NULL;
104                 break;
105             }
106         }
107         /* we are ready, try to connect */
108         if (connect(i, addr->ai_addr, addr->ai_addrlen) == 0)
109             break; /* success */
110         oerrno = errno;
111         close(i);
112         errno = oerrno;
113     }
114     freeaddrinfo(ressave);
115
116     if (addr == NULL) {
117         /* all connect(2) calls failed or some other error has occurred */
118         oerrno = errno;
119         close(i);
120         store_error(buff, "connect(s) failed: ", strerror(oerrno));
121         errno = oerrno;
122         return -1;
123     }
124     {
125 #else /* HAVE_INET6 */
126     if (inet_aton(host, &quadaddr)) {
127         /* Host was specified as a dotted-quad internet address.  Fill in
128          * the parts of the hostent struct that we need. */
129         fakehp.h_length = sizeof quadaddr;
130         fakehp.h_addrtype = AF_INET;
131         hp = &fakehp;
132         fakelist[0] = (char *)&quadaddr;
133         fakelist[1] = NULL;
134         ap = fakelist;
135     }
136     else if ((hp = gethostbyname(host)) != NULL)
137         ap = hp->h_addr_list;
138     else {
139         /* Not a host name. */
140         store_error(buff, "gethostbyname failed: ", hstrerror(h_errno));
141         errno = 0;
142         return -1;
143     }
144
145     /* Set up the socket address. */
146     memset(&server, 0, sizeof server);
147     server.sin_family = hp->h_addrtype;
148 #ifdef HAVE_SOCKADDR_LEN
149     server.sin_len = sizeof( struct sockaddr_in );
150 #endif
151     server.sin_port = htons(port);
152
153     /* Source IP address to which we bind. */
154     memset(&client, 0, sizeof client);
155     client.sin_family = AF_INET;
156 #ifdef HAVE_SOCKADDR_LEN
157     client.sin_len = sizeof( struct sockaddr_in );
158 #endif
159     if (innconf->sourceaddress) {
160         if (!inet_aton(innconf->sourceaddress, &client.sin_addr)) {
161             store_error(buff, "inet_aton failed on innconf's"
162                         " source address: ", innconf->sourceaddress);
163             errno = EINVAL;
164             return -1;
165         }
166     } else
167         client.sin_addr.s_addr = htonl(INADDR_ANY);
168
169     /* Loop through the address list, trying to connect. */
170     for (; ap && *ap; ap++) {
171         /* Make a socket and try to connect. */
172         if ((i = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {
173             if (!buff[0]) {
174                 oerrno = errno;
175                 store_error(buff, "create socket failed: ", strerror(oerrno));
176                 errno = oerrno;
177             }
178             break;
179         }
180         /* Bind to the source address we want. */
181         if (bind(i, (struct sockaddr *)&client, sizeof client) < 0) {
182             oerrno = errno;
183             close(i);
184             store_error(buff, "bind failed: ", strerror(oerrno));
185             errno = oerrno;
186             continue;
187         }
188         /* Copy the address via inline memcpy:
189          *      memcpy(&server.sin_addr, *ap, hp->h_length); */
190         p = (char *)*ap;
191         for (dest = (char *)&server.sin_addr, j = hp->h_length; --j >= 0; )
192             *dest++ = *p++;
193         if (connect(i, (struct sockaddr *)&server, sizeof server) < 0) {
194             oerrno = errno;
195             close(i);
196             store_error(buff, "connect failed: ", strerror(oerrno));
197             errno = oerrno;
198             continue;
199         }
200 #endif /* HAVE_INET6 */
201
202         /* Connected -- now make sure we can post. */
203         if ((F = fdopen(i, "r")) == NULL) {
204             oerrno = errno;
205             close(i);
206             errno = oerrno;
207             store_error(buff, "fdopen failed: ", strerror(oerrno));
208             errno = EINVAL;
209             return -1;
210         }
211         if (fgets(buff, sizeof mybuff, F) == NULL) {
212             if (ferror(F)) store_error(buff, "peer closed connection","");
213             else store_error(buff, "fgets failed: ", strerror(errno));
214             oerrno = errno;
215             fclose(F);
216             errno = oerrno;
217             return -1;
218         }
219         j = atoi(buff);
220         if (j != NNTP_POSTOK_VAL && j != NNTP_NOPOSTOK_VAL) {
221             fclose(F);
222             /* This seems like a reasonable error code to use... */
223             errno = EPERM;
224             return -1;
225         }
226
227         *FromServerp = F;
228         if ((*ToServerp = fdopen(dup(i), "w")) == NULL) {
229             oerrno = errno;
230             fclose(F);
231             store_error(buff, "fdopen/dup failed: ", strerror(oerrno));
232             errno = oerrno;
233             return -1;
234         }
235         return 0;
236     }
237
238     if (!buff[0])
239         store_error(buff, "no address(es) for host","");
240
241     return -1;
242 }
243
244 int NNTPremoteopen(int port, FILE **FromServerp, FILE **ToServerp, char *errbuff)
245 {
246     char                *p;
247
248     if ((p = innconf->server) == NULL) {
249         if (errbuff)
250             strcpy(errbuff, "What server?");
251         return -1;
252     }
253     return NNTPconnect(p, port, FromServerp, ToServerp, errbuff);
254 }