/***************************************************************************\
*                                                                           *
*  BitlBee - An IRC to IM gateway                                           *
*  Utility functions for file transfer                                      *
*                                                                           *
*  Copyright 2008 Uli Meis <a.sporto+bee@gmail.com>                         *
*                                                                           *
*  This program is free software; you can redistribute it and/or modify     *
*  it under the terms of the GNU General Public License as published by     *
*  the Free Software Foundation; either version 2 of the License, or        *
*  (at your option) any later version.                                      *
*                                                                           *
*  This program is distributed in the hope that it will be useful,          *
*  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
*  GNU General Public License for more details.                             *
*                                                                           *
*  You should have received a copy of the GNU General Public License along  *
*  with this program; if not, write to the Free Software Foundation, Inc.,  *
*  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.              *
*                                                                           *
\***************************************************************************/

#define BITLBEE_CORE
#include "bitlbee.h"
#include <poll.h>
#include <netinet/tcp.h>
#include "lib/ftutil.h"

#define ASSERTSOCKOP(op, msg) \
	if ((op) == -1) { \
		g_snprintf(errmsg, sizeof(errmsg), msg ": %s", strerror(errno)); \
		return -1; }

/*
 * Creates a listening socket and returns it in saddr_ptr.
 */
int ft_listen(struct sockaddr_storage *saddr_ptr, char *host, char *port, int copy_fd, int for_bitlbee_client,
              char **errptr)
{
	int fd, gret, saddrlen;
	struct addrinfo hints, *rp;
	socklen_t ssize = sizeof(struct sockaddr_storage);
	struct sockaddr_storage saddrs = {0}, *saddr = &saddrs;
	static char errmsg[1024];
	char *ftlisten = global.conf->ft_listen;

	if (errptr) {
		*errptr = errmsg;
	}

	strcpy(port, "0");

	/* Format is <IP-A>[:<Port-A>];<IP-B>[:<Port-B>] where
	 * A is for connections with the bitlbee client (DCC)
	 * and B is for connections with IM peers.
	 */
	if (ftlisten) {
		char *scolon = strchr(ftlisten, ';');
		char *colon;

		if (scolon) {
			if (for_bitlbee_client) {
				*scolon = '\0';
				strncpy(host, ftlisten, NI_MAXHOST);
				*scolon = ';';
			} else {
				strncpy(host, scolon + 1, NI_MAXHOST);
			}
		} else {
			strncpy(host, ftlisten, NI_MAXHOST);
		}

		if ((colon = strchr(host, ':'))) {
			*colon = '\0';
			strncpy(port, colon + 1, 5);
		}
	} else if (copy_fd >= 0 && getsockname(copy_fd, (struct sockaddr*) &saddrs, &ssize) == 0 &&
	           (saddrs.ss_family == AF_INET || saddrs.ss_family == AF_INET6) &&
	           getnameinfo((struct sockaddr*) &saddrs, ssize, host, NI_MAXHOST,
	                       NULL, 0, NI_NUMERICHOST) == 0) {
		/* We just took our local address on copy_fd, which is likely to be a
		   sensible address from which we can do a file transfer now - the
		   most sensible we can get easily. */
	} else {
		ASSERTSOCKOP(gethostname(host, NI_MAXHOST), "gethostname()");
	}

	memset(&hints, 0, sizeof(struct addrinfo));
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_NUMERICSERV;

	if ((gret = getaddrinfo(host, port, &hints, &rp)) != 0) {
		sprintf(errmsg, "getaddrinfo() failed: %s", gai_strerror(gret));
		return -1;
	}

	saddrlen = rp->ai_addrlen;

	memcpy(saddr, rp->ai_addr, saddrlen);

	freeaddrinfo(rp);

	ASSERTSOCKOP(fd = socket(saddr->ss_family, SOCK_STREAM, 0), "Opening socket");
	ASSERTSOCKOP(bind(fd, ( struct sockaddr *) saddr, saddrlen), "Binding socket");
	ASSERTSOCKOP(listen(fd, 1), "Making socket listen");

	if (!inet_ntop(saddr->ss_family, saddr->ss_family == AF_INET ?
	               ( void * ) &(( struct sockaddr_in * ) saddr)->sin_addr.s_addr :
	               ( void * ) &(( struct sockaddr_in6 * ) saddr)->sin6_addr.s6_addr,
	               host, NI_MAXHOST)) {
		strcpy(errmsg, "inet_ntop failed on listening socket");
		return -1;
	}

	ssize = sizeof(struct sockaddr_storage);
	ASSERTSOCKOP(getsockname(fd, ( struct sockaddr *) saddr, &ssize), "Getting socket name");

	if (saddr->ss_family == AF_INET) {
		g_snprintf(port, 6, "%d", ntohs(((struct sockaddr_in *) saddr)->sin_port));
	} else {
		g_snprintf(port, 6, "%d", ntohs(((struct sockaddr_in6 *) saddr)->sin6_port));
	}

	if (saddr_ptr) {
		memcpy(saddr_ptr, saddr, saddrlen);
	}

	/* I hate static-length strings.. */
	host[NI_MAXHOST - 1] = '\0';
	port[5] = '\0';

	return fd;
}
