Blame src/resolver.c

Packit c32a2d
/*
Packit c32a2d
	resolver.c: TCP network stuff, for IPv4 and IPv6
Packit c32a2d
	Oh, and also some URL parsing... extracting host name and such.
Packit c32a2d
Packit c32a2d
	copyright 2008-2010 by the mpg123 project - free software under the terms of the LGPL 2.1
Packit c32a2d
	see COPYING and AUTHORS files in distribution or http://mpg123.org
Packit c32a2d
	initially written Thomas Orgis (based on httpget.c)
Packit c32a2d
Packit c32a2d
	The idea is to have everything involving the game between URLs and IPs/connections separated here.
Packit c32a2d
	I begin with the outsourcing of IPv4 stuff, then make the stuff generic.
Packit c32a2d
*/
Packit c32a2d
Packit c32a2d
/* Newer glibc is strict about this: getaddrinfo() stuff needs POSIX2K. */
Packit c32a2d
#define _POSIX_C_SOURCE 200112L
Packit c32a2d
Packit c32a2d
#include "mpg123app.h"
Packit c32a2d
Packit c32a2d
#ifdef NETWORK
Packit c32a2d
#include "true.h"
Packit c32a2d
#include "resolver.h"
Packit c32a2d
#if !defined (WANT_WIN32_SOCKETS)
Packit c32a2d
#include <netdb.h>
Packit c32a2d
#include <sys/param.h>
Packit c32a2d
#include <sys/socket.h>
Packit c32a2d
#include <netinet/in.h>
Packit c32a2d
#include <arpa/inet.h>
Packit c32a2d
#include <errno.h>
Packit c32a2d
#endif
Packit c32a2d
#ifdef HAVE_SYS_SELECT_H
Packit c32a2d
#include <sys/select.h>
Packit c32a2d
#endif
Packit c32a2d
#ifdef HAVE_SYS_TIME_H
Packit c32a2d
#include <sys/time.h>
Packit c32a2d
#endif
Packit c32a2d
#ifdef HAVE_SYS_TYPES_H
Packit c32a2d
#include <sys/types.h>
Packit c32a2d
#endif
Packit c32a2d
#include <unistd.h>
Packit c32a2d
#include "debug.h"
Packit c32a2d
Packit c32a2d
int split_url(mpg123_string *url, mpg123_string *auth, mpg123_string *host, mpg123_string *port, mpg123_string *path)
Packit c32a2d
{
Packit c32a2d
	size_t pos  = 0; /* current position in input URL */
Packit c32a2d
	size_t pos2 = 0; /* another position in input URL */
Packit c32a2d
#ifdef IPV6
Packit c32a2d
	size_t pos3 = 0; /* yet another (for IPv6 port)   */
Packit c32a2d
#endif
Packit c32a2d
	char *part  = NULL; /* a part of url we work on */
Packit c32a2d
	int ret = TRUE; /* return code */
Packit c32a2d
	/* Zeroing the output strings; not freeing to avoid unnecessary mallocs. */
Packit c32a2d
	if(auth) auth->fill = 0;
Packit c32a2d
	if(host) host->fill = 0;
Packit c32a2d
	if(port) port->fill = 0;
Packit c32a2d
	if(path) path->fill = 0;
Packit c32a2d
Packit c32a2d
	if(!url || !url->fill || url->p[url->fill-1] != 0)
Packit c32a2d
	{
Packit c32a2d
		error("URL string is not good! (Programmer's fault!?)");
Packit c32a2d
		return FALSE;
Packit c32a2d
	}
Packit c32a2d
	if (!(strncmp(url->p+pos, "http://", 7)))
Packit c32a2d
	pos += 7; /* Drop protocol. */
Packit c32a2d
Packit c32a2d
	/* Extract user[:passwd]@... */
Packit c32a2d
	if( (part = strchr(url->p+pos,'@')) )
Packit c32a2d
	{
Packit c32a2d
		size_t i;
Packit c32a2d
		size_t partlen = part - url->p - pos;
Packit c32a2d
		int have_auth = TRUE;
Packit c32a2d
		/* User names or passwords don't have "/" in them (?), the "@" wasn't for real if we find such. */
Packit c32a2d
		for(i=0;i
Packit c32a2d
		{
Packit c32a2d
			if(url->p[pos+i] == '/' )
Packit c32a2d
			{
Packit c32a2d
				have_auth = FALSE;
Packit c32a2d
				break;
Packit c32a2d
			}
Packit c32a2d
		}
Packit c32a2d
		if(have_auth)
Packit c32a2d
		{
Packit c32a2d
			if(auth != NULL && !mpg123_set_substring(auth, url->p, pos, partlen))
Packit c32a2d
			{
Packit c32a2d
				error("Cannot set auth string (out of mem?).");
Packit c32a2d
				return FALSE;
Packit c32a2d
			}
Packit c32a2d
			pos += partlen+1; /* Continuing after the "@". */
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
	
Packit c32a2d
	/* Extract host name or IP. */
Packit c32a2d
#ifdef IPV6
Packit c32a2d
	if(url->p[pos] == '[')
Packit c32a2d
	{ /* It's possibly an IPv6 url in [ ] */
Packit c32a2d
		++pos;
Packit c32a2d
		if( (part = strchr(url->p+pos,']')) != NULL)
Packit c32a2d
		{
Packit c32a2d
			pos2 = part-url->p;
Packit c32a2d
			pos3 = pos2+1; /* : after ] */
Packit c32a2d
		}
Packit c32a2d
		else { error("Malformed IPv6 URL!"); return FALSE; }
Packit c32a2d
	}
Packit c32a2d
	else
Packit c32a2d
	{
Packit c32a2d
#endif
Packit c32a2d
	for(pos2=pos; pos2 < url->fill-1; ++pos2)
Packit c32a2d
	{
Packit c32a2d
		char a = url->p[pos2];
Packit c32a2d
		if( a == ':' || a == '/') break;
Packit c32a2d
	}
Packit c32a2d
#ifdef IPV6
Packit c32a2d
		pos3 = pos2;
Packit c32a2d
	}
Packit c32a2d
#endif
Packit c32a2d
	/* At pos2 there is now either a delimiter or the end. */
Packit c32a2d
debug4("hostname between %lu and %lu, %lu chars of %s", (unsigned long)pos, (unsigned long)pos2, (unsigned long)(pos2-pos), url->p + pos);
Packit c32a2d
	if(host != NULL && !mpg123_set_substring(host, url->p, pos, pos2-pos))
Packit c32a2d
	{
Packit c32a2d
		error("Cannot set host string (out of mem?).");
Packit c32a2d
		return FALSE;
Packit c32a2d
	}
Packit c32a2d
#ifdef IPV6
Packit c32a2d
	pos = pos3; /* Look after ], if present. */
Packit c32a2d
#else
Packit c32a2d
	pos = pos2;
Packit c32a2d
#endif
Packit c32a2d
Packit c32a2d
	/* Now for the port... */
Packit c32a2d
	if(url->p[pos] == ':')
Packit c32a2d
	{
Packit c32a2d
		pos += 1; /* We begin _after_ the ":". */
Packit c32a2d
		for(pos2=pos; pos2 < url->fill-1; ++pos2)
Packit c32a2d
		if( url->p[pos2] == '/' ) break;
Packit c32a2d
Packit c32a2d
		/* Check for port being numbers? Not sure if that's needed. */
Packit c32a2d
		if(port) ret = mpg123_set_substring(port, url->p, pos, pos2-pos);
Packit c32a2d
		pos = pos2;
Packit c32a2d
	}
Packit c32a2d
	else if(port) ret = mpg123_set_string(port, "80");
Packit c32a2d
Packit c32a2d
	if(!ret)
Packit c32a2d
	{
Packit c32a2d
		error("Cannot set port string (out of mem?).");
Packit c32a2d
		return FALSE;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	/* Now only the path is left.
Packit c32a2d
	   If there is no path at all, assume "/" */
Packit c32a2d
	if(path) ret = url->p[pos] == 0
Packit c32a2d
		? mpg123_set_string(path, "/")
Packit c32a2d
		: mpg123_set_substring(path, url->p, pos, url->fill-1-pos);
Packit c32a2d
Packit c32a2d
	if(!ret) error("Cannot set path string (out of mem?)");
Packit c32a2d
Packit c32a2d
	return ret;
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* Switch between blocking and non-blocking mode. */
Packit c32a2d
#if !defined (WANT_WIN32_SOCKETS)
Packit c32a2d
static void nonblock(int sock)
Packit c32a2d
{
Packit c32a2d
	int flags = fcntl(sock, F_GETFL);
Packit c32a2d
	flags |= O_NONBLOCK;
Packit c32a2d
	fcntl(sock, F_SETFL, flags);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
static void block(int sock)
Packit c32a2d
{
Packit c32a2d
	int flags = fcntl(sock, F_GETFL);
Packit c32a2d
	flags &= ~O_NONBLOCK;
Packit c32a2d
	fcntl(sock, F_SETFL, flags);
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
/* If we want a timeout, connect non-blocking and wait for that long... */
Packit c32a2d
static int timeout_connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
Packit c32a2d
{
Packit c32a2d
	if(param.timeout > 0)
Packit c32a2d
	{
Packit c32a2d
		int err;
Packit c32a2d
		nonblock(sockfd);
Packit c32a2d
		err = connect(sockfd, serv_addr, addrlen);
Packit c32a2d
		if(err == 0)
Packit c32a2d
		{
Packit c32a2d
			debug("immediately successful");
Packit c32a2d
			block(sockfd);
Packit c32a2d
			return 0;
Packit c32a2d
		}
Packit c32a2d
		else if(errno == EINPROGRESS)
Packit c32a2d
		{
Packit c32a2d
			struct timeval tv;
Packit c32a2d
			fd_set fds;
Packit c32a2d
			tv.tv_sec = param.timeout;
Packit c32a2d
			tv.tv_usec = 0;
Packit c32a2d
Packit c32a2d
			debug("in progress, waiting...");
Packit c32a2d
Packit c32a2d
			FD_ZERO(&fds);
Packit c32a2d
			FD_SET(sockfd, &fds);
Packit c32a2d
			err = select(sockfd+1, NULL, &fds, NULL, &tv;;
Packit c32a2d
			if(err > 0)
Packit c32a2d
			{
Packit c32a2d
				socklen_t len = sizeof(err);
Packit c32a2d
				if(   (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &err, &len) == 0)
Packit c32a2d
				   && (err == 0) )
Packit c32a2d
				{
Packit c32a2d
					debug("non-blocking connect has been successful");
Packit c32a2d
					block(sockfd);
Packit c32a2d
					return 0;
Packit c32a2d
				}
Packit c32a2d
				else
Packit c32a2d
				{
Packit c32a2d
					error1("connection error: %s", strerror(err));
Packit c32a2d
					return -1;
Packit c32a2d
				}
Packit c32a2d
			}
Packit c32a2d
			else if(err == 0)
Packit c32a2d
			{
Packit c32a2d
				error("connection timed out");
Packit c32a2d
				return -1;
Packit c32a2d
			}
Packit c32a2d
			else
Packit c32a2d
			{
Packit c32a2d
				error1("error from select(): %s", strerror(errno));
Packit c32a2d
				return -1;
Packit c32a2d
			}
Packit c32a2d
		}
Packit c32a2d
		else
Packit c32a2d
		{
Packit c32a2d
			error1("connection failed: %s", strerror(errno));
Packit c32a2d
			return err;
Packit c32a2d
		}
Packit c32a2d
	}
Packit c32a2d
	else
Packit c32a2d
	{
Packit c32a2d
		if(connect(sockfd, serv_addr, addrlen))
Packit c32a2d
		{
Packit c32a2d
			error1("connection failed: %s", strerror(errno));
Packit c32a2d
			return -1;
Packit c32a2d
		}
Packit c32a2d
		else return 0; /* _good_ */
Packit c32a2d
	}
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
Packit c32a2d
/* So, this then is the only routine that should know about IPv4 or v6 in future. */
Packit c32a2d
int open_connection(mpg123_string *host, mpg123_string *port)
Packit c32a2d
{
Packit c32a2d
#ifndef IPV6 /* The legacy code for IPv4. No real change to keep all compatibility. */
Packit c32a2d
#ifndef INADDR_NONE
Packit c32a2d
#define INADDR_NONE 0xffffffff
Packit c32a2d
#endif
Packit c32a2d
	struct sockaddr_in server;
Packit c32a2d
	struct hostent *myhostent;
Packit c32a2d
	struct in_addr myaddr;
Packit c32a2d
	int isip = 1;
Packit c32a2d
	char *cptr = host->p;
Packit c32a2d
	int sock = -1;
Packit c32a2d
	if(param.verbose>1) fprintf(stderr, "Note: Attempting old-style connection to %s\n", host->p);
Packit c32a2d
	/* Resolve to IP; parse port number. */
Packit c32a2d
	while(*cptr) /* Iterate over characters of hostname, check if it's an IP or name. */
Packit c32a2d
	{
Packit c32a2d
		if ((*cptr < '0' || *cptr > '9') && *cptr != '.')
Packit c32a2d
		{
Packit c32a2d
			isip = 0;
Packit c32a2d
			break;
Packit c32a2d
		}
Packit c32a2d
		cptr++;
Packit c32a2d
	}
Packit c32a2d
	if(!isip)
Packit c32a2d
	{ /* Name lookup. */
Packit c32a2d
		if (!(myhostent = gethostbyname(host->p))) return -1;
Packit c32a2d
Packit c32a2d
		memcpy (&myaddr, myhostent->h_addr, sizeof(myaddr));
Packit c32a2d
		server.sin_addr.s_addr = myaddr.s_addr;
Packit c32a2d
	}
Packit c32a2d
	else  /* Just use the IP. */
Packit c32a2d
	if((server.sin_addr.s_addr = inet_addr(host->p)) == INADDR_NONE)
Packit c32a2d
	return -1;
Packit c32a2d
Packit c32a2d
	server.sin_port = htons(atoi(port->p));
Packit c32a2d
	server.sin_family = AF_INET;
Packit c32a2d
Packit c32a2d
	if((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0)
Packit c32a2d
	{
Packit c32a2d
		error1("Cannot create socket: %s", strerror(errno));
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
	if(timeout_connect(sock, (struct sockaddr *)&server, sizeof(server)))
Packit c32a2d
	return -1;
Packit c32a2d
#else /* Host lookup and connection in a protocol independent manner. */
Packit c32a2d
	struct addrinfo hints;
Packit c32a2d
	struct addrinfo *addr, *addrlist;
Packit c32a2d
	int ret, sock = -1;
Packit c32a2d
Packit c32a2d
	if(param.verbose>1) fprintf(stderr, "Note: Attempting new-style connection to %s\n", host->p);
Packit c32a2d
	memset(&hints, 0, sizeof(struct addrinfo));
Packit c32a2d
	hints.ai_family   = AF_UNSPEC; /* We accept both IPv4 and IPv6 ... and perhaps IPv8;-) */
Packit c32a2d
	hints.ai_socktype = SOCK_STREAM;
Packit c32a2d
	hints.ai_flags = 0;
Packit c32a2d
#ifdef HAVE_GAI_ADDRCONFIG
Packit c32a2d
	hints.ai_flags |= AI_ADDRCONFIG; /* Only ask for addresses that we have configured interfaces for. */
Packit c32a2d
#endif
Packit c32a2d
	ret = getaddrinfo(host->p, port->p, &hints, &addrlist);
Packit c32a2d
	if(ret != 0)
Packit c32a2d
	{
Packit c32a2d
		error3("Resolving %s:%s: %s", host->p, port->p, gai_strerror(ret));
Packit c32a2d
		return -1;
Packit c32a2d
	}
Packit c32a2d
Packit c32a2d
	addr = addrlist;
Packit c32a2d
	while(addr != NULL)
Packit c32a2d
	{
Packit c32a2d
		sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
Packit c32a2d
		if(sock >= 0)
Packit c32a2d
		{
Packit c32a2d
			if(timeout_connect(sock, addr->ai_addr, addr->ai_addrlen) == 0)
Packit c32a2d
			break;
Packit c32a2d
Packit c32a2d
			close(sock);
Packit c32a2d
			sock=-1;
Packit c32a2d
		}
Packit c32a2d
		addr=addr->ai_next;
Packit c32a2d
	}
Packit c32a2d
	if(sock < 0) error2("Cannot resolve/connect to %s:%s!", host->p, port->p);
Packit c32a2d
Packit c32a2d
	freeaddrinfo(addrlist);
Packit c32a2d
#endif
Packit c32a2d
	return sock; /* Hopefully, that's an open socket to talk with. */
Packit c32a2d
}
Packit c32a2d
#endif /* !defined (WANT_WIN32_SOCKETS) */
Packit c32a2d
#else /* NETWORK */
Packit c32a2d
Packit c32a2d
void resolver_dummy_without_sense()
Packit c32a2d
{
Packit c32a2d
	/* Some compilers don't like empty source files. */
Packit c32a2d
}
Packit c32a2d
Packit c32a2d
#endif