1 /*
2 	resolver.c: TCP network stuff, for IPv4 and IPv6
3 	Oh, and also some URL parsing... extracting host name and such.
4 
5 	copyright 2008-2010 by the mpg123 project - free software under the terms of the LGPL 2.1
6 	see COPYING and AUTHORS files in distribution or http://mpg123.org
7 	initially written Thomas Orgis (based on httpget.c)
8 
9 	The idea is to have everything involving the game between URLs and IPs/connections separated here.
10 	I begin with the outsourcing of IPv4 stuff, then make the stuff generic.
11 */
12 
13 /* Newer glibc is strict about this: getaddrinfo() stuff needs POSIX2K. */
14 #define _POSIX_C_SOURCE 200112L
15 
16 #include "mpg123app.h"
17 
18 #ifdef NETWORK
19 #include "true.h"
20 #include "resolver.h"
21 #if !defined (WANT_WIN32_SOCKETS)
22 #include <netdb.h>
23 #include <sys/param.h>
24 #include <sys/socket.h>
25 #include <netinet/in.h>
26 #include <arpa/inet.h>
27 #include <errno.h>
28 #endif
29 #ifdef HAVE_SYS_SELECT_H
30 #include <sys/select.h>
31 #endif
32 #ifdef HAVE_SYS_TIME_H
33 #include <sys/time.h>
34 #endif
35 #ifdef HAVE_SYS_TYPES_H
36 #include <sys/types.h>
37 #endif
38 #include <unistd.h>
39 #include "debug.h"
40 
split_url(mpg123_string * url,mpg123_string * auth,mpg123_string * host,mpg123_string * port,mpg123_string * path)41 int split_url(mpg123_string *url, mpg123_string *auth, mpg123_string *host, mpg123_string *port, mpg123_string *path)
42 {
43 	size_t pos  = 0; /* current position in input URL */
44 	size_t pos2 = 0; /* another position in input URL */
45 #ifdef IPV6
46 	size_t pos3 = 0; /* yet another (for IPv6 port)   */
47 #endif
48 	char *part  = NULL; /* a part of url we work on */
49 	int ret = TRUE; /* return code */
50 	/* Zeroing the output strings; not freeing to avoid unnecessary mallocs. */
51 	if(auth) auth->fill = 0;
52 	if(host) host->fill = 0;
53 	if(port) port->fill = 0;
54 	if(path) path->fill = 0;
55 
56 	if(!url || !url->fill || url->p[url->fill-1] != 0)
57 	{
58 		error("URL string is not good! (Programmer's fault!?)");
59 		return FALSE;
60 	}
61 	if (!(strncmp(url->p+pos, "http://", 7)))
62 	pos += 7; /* Drop protocol. */
63 
64 	/* Extract user[:passwd]@... */
65 	if( (part = strchr(url->p+pos,'@')) )
66 	{
67 		size_t i;
68 		size_t partlen = part - url->p - pos;
69 		int have_auth = TRUE;
70 		/* User names or passwords don't have "/" in them (?), the "@" wasn't for real if we find such. */
71 		for(i=0;i<partlen;i++)
72 		{
73 			if(url->p[pos+i] == '/' )
74 			{
75 				have_auth = FALSE;
76 				break;
77 			}
78 		}
79 		if(have_auth)
80 		{
81 			if(auth != NULL && !mpg123_set_substring(auth, url->p, pos, partlen))
82 			{
83 				error("Cannot set auth string (out of mem?).");
84 				return FALSE;
85 			}
86 			pos += partlen+1; /* Continuing after the "@". */
87 		}
88 	}
89 
90 	/* Extract host name or IP. */
91 #ifdef IPV6
92 	if(url->p[pos] == '[')
93 	{ /* It's possibly an IPv6 url in [ ] */
94 		++pos;
95 		if( (part = strchr(url->p+pos,']')) != NULL)
96 		{
97 			pos2 = part-url->p;
98 			pos3 = pos2+1; /* : after ] */
99 		}
100 		else { error("Malformed IPv6 URL!"); return FALSE; }
101 	}
102 	else
103 	{
104 #endif
105 	for(pos2=pos; pos2 < url->fill-1; ++pos2)
106 	{
107 		char a = url->p[pos2];
108 		if( a == ':' || a == '/') break;
109 	}
110 #ifdef IPV6
111 		pos3 = pos2;
112 	}
113 #endif
114 	/* At pos2 there is now either a delimiter or the end. */
115 debug4("hostname between %lu and %lu, %lu chars of %s", (unsigned long)pos, (unsigned long)pos2, (unsigned long)(pos2-pos), url->p + pos);
116 	if(host != NULL && !mpg123_set_substring(host, url->p, pos, pos2-pos))
117 	{
118 		error("Cannot set host string (out of mem?).");
119 		return FALSE;
120 	}
121 #ifdef IPV6
122 	pos = pos3; /* Look after ], if present. */
123 #else
124 	pos = pos2;
125 #endif
126 
127 	/* Now for the port... */
128 	if(url->p[pos] == ':')
129 	{
130 		pos += 1; /* We begin _after_ the ":". */
131 		for(pos2=pos; pos2 < url->fill-1; ++pos2)
132 		if( url->p[pos2] == '/' ) break;
133 
134 		/* Check for port being numbers? Not sure if that's needed. */
135 		if(port) ret = mpg123_set_substring(port, url->p, pos, pos2-pos);
136 		pos = pos2;
137 	}
138 	else if(port) ret = mpg123_set_string(port, "80");
139 
140 	if(!ret)
141 	{
142 		error("Cannot set port string (out of mem?).");
143 		return FALSE;
144 	}
145 
146 	/* Now only the path is left.
147 	   If there is no path at all, assume "/" */
148 	if(path) ret = url->p[pos] == 0
149 		? mpg123_set_string(path, "/")
150 		: mpg123_set_substring(path, url->p, pos, url->fill-1-pos);
151 
152 	if(!ret) error("Cannot set path string (out of mem?)");
153 
154 	return ret;
155 }
156 
157 /* Switch between blocking and non-blocking mode. */
158 #if !defined (WANT_WIN32_SOCKETS)
nonblock(int sock)159 static void nonblock(int sock)
160 {
161 	int flags = fcntl(sock, F_GETFL);
162 	flags |= O_NONBLOCK;
163 	fcntl(sock, F_SETFL, flags);
164 }
165 
block(int sock)166 static void block(int sock)
167 {
168 	int flags = fcntl(sock, F_GETFL);
169 	flags &= ~O_NONBLOCK;
170 	fcntl(sock, F_SETFL, flags);
171 }
172 
173 /* If we want a timeout, connect non-blocking and wait for that long... */
timeout_connect(int sockfd,const struct sockaddr * serv_addr,socklen_t addrlen)174 static int timeout_connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
175 {
176 	if(param.timeout > 0)
177 	{
178 		int err;
179 		nonblock(sockfd);
180 		err = connect(sockfd, serv_addr, addrlen);
181 		if(err == 0)
182 		{
183 			debug("immediately successful");
184 			block(sockfd);
185 			return 0;
186 		}
187 		else if(errno == EINPROGRESS)
188 		{
189 			struct timeval tv;
190 			fd_set fds;
191 			tv.tv_sec = param.timeout;
192 			tv.tv_usec = 0;
193 
194 			debug("in progress, waiting...");
195 
196 			FD_ZERO(&fds);
197 			FD_SET(sockfd, &fds);
198 			err = select(sockfd+1, NULL, &fds, NULL, &tv);
199 			if(err > 0)
200 			{
201 				socklen_t len = sizeof(err);
202 				if(   (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len) == 0)
203 				   && (err == 0) )
204 				{
205 					debug("non-blocking connect has been successful");
206 					block(sockfd);
207 					return 0;
208 				}
209 				else
210 				{
211 					error1("connection error: %s", strerror(err));
212 					return -1;
213 				}
214 			}
215 			else if(err == 0)
216 			{
217 				error("connection timed out");
218 				return -1;
219 			}
220 			else
221 			{
222 				error1("error from select(): %s", strerror(errno));
223 				return -1;
224 			}
225 		}
226 		else
227 		{
228 			error1("connection failed: %s", strerror(errno));
229 			return err;
230 		}
231 	}
232 	else
233 	{
234 		if(connect(sockfd, serv_addr, addrlen))
235 		{
236 			error1("connection failed: %s", strerror(errno));
237 			return -1;
238 		}
239 		else return 0; /* _good_ */
240 	}
241 }
242 
243 
244 /* So, this then is the only routine that should know about IPv4 or v6 in future. */
open_connection(mpg123_string * host,mpg123_string * port)245 int open_connection(mpg123_string *host, mpg123_string *port)
246 {
247 #ifndef IPV6 /* The legacy code for IPv4. No real change to keep all compatibility. */
248 #ifndef INADDR_NONE
249 #define INADDR_NONE 0xffffffff
250 #endif
251 	struct sockaddr_in server;
252 	struct hostent *myhostent;
253 	struct in_addr myaddr;
254 	int isip = 1;
255 	char *cptr = host->p;
256 	int sock = -1;
257 	if(param.verbose>1) fprintf(stderr, "Note: Attempting old-style connection to %s\n", host->p);
258 	/* Resolve to IP; parse port number. */
259 	while(*cptr) /* Iterate over characters of hostname, check if it's an IP or name. */
260 	{
261 		if ((*cptr < '0' || *cptr > '9') && *cptr != '.')
262 		{
263 			isip = 0;
264 			break;
265 		}
266 		cptr++;
267 	}
268 	if(!isip)
269 	{ /* Name lookup. */
270 		if (!(myhostent = gethostbyname(host->p))) return -1;
271 
272 		memcpy (&myaddr, myhostent->h_addr, sizeof(myaddr));
273 		server.sin_addr.s_addr = myaddr.s_addr;
274 	}
275 	else  /* Just use the IP. */
276 	if((server.sin_addr.s_addr = inet_addr(host->p)) == INADDR_NONE)
277 	return -1;
278 
279 	server.sin_port = htons(atoi(port->p));
280 	server.sin_family = AF_INET;
281 
282 	if((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0)
283 	{
284 		error1("Cannot create socket: %s", strerror(errno));
285 		return -1;
286 	}
287 	if(timeout_connect(sock, (struct sockaddr *)&server, sizeof(server)))
288 	return -1;
289 #else /* Host lookup and connection in a protocol independent manner. */
290 	struct addrinfo hints;
291 	struct addrinfo *addr, *addrlist;
292 	int ret, sock = -1;
293 
294 	if(param.verbose>1) fprintf(stderr, "Note: Attempting new-style connection to %s\n", host->p);
295 	memset(&hints, 0, sizeof(struct addrinfo));
296 	hints.ai_family   = AF_UNSPEC; /* We accept both IPv4 and IPv6 ... and perhaps IPv8;-) */
297 	hints.ai_socktype = SOCK_STREAM;
298 	hints.ai_flags = 0;
299 #ifdef HAVE_GAI_ADDRCONFIG
300 	hints.ai_flags |= AI_ADDRCONFIG; /* Only ask for addresses that we have configured interfaces for. */
301 #endif
302 	ret = getaddrinfo(host->p, port->p, &hints, &addrlist);
303 	if(ret != 0)
304 	{
305 		error3("Resolving %s:%s: %s", host->p, port->p, gai_strerror(ret));
306 		return -1;
307 	}
308 
309 	addr = addrlist;
310 	while(addr != NULL)
311 	{
312 		sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
313 		if(sock >= 0)
314 		{
315 			if(timeout_connect(sock, addr->ai_addr, addr->ai_addrlen) == 0)
316 			break;
317 
318 			close(sock);
319 			sock=-1;
320 		}
321 		addr=addr->ai_next;
322 	}
323 	if(sock < 0) error2("Cannot resolve/connect to %s:%s!", host->p, port->p);
324 
325 	freeaddrinfo(addrlist);
326 #endif
327 	return sock; /* Hopefully, that's an open socket to talk with. */
328 }
329 #endif /* !defined (WANT_WIN32_SOCKETS) */
330 #else /* NETWORK */
331 
resolver_dummy_without_sense()332 void resolver_dummy_without_sense()
333 {
334 	/* Some compilers don't like empty source files. */
335 }
336 
337 #endif
338