/*	$Id: InetTransport.c++ 928 2009-05-25 20:20:10Z faxguy $ */
/*
 * Copyright (c) 1995-1996 Sam Leffler
 * Copyright (c) 1995-1996 Silicon Graphics, Inc.
 * HylaFAX is a trademark of Silicon Graphics
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */
#include "config.h"
#include "FaxClient.h"
#include "InetTransport.h"
#include "Sys.h"

InetTransport::InetTransport(FaxClient& c) : Transport(c) {}
InetTransport::~InetTransport(){}

bool
InetTransport::isA(const char*)
{
     return (true);			// XXX are there checks we can make?
}

#if CONFIG_INETTRANSPORT
#include "Socket.h"

#include <sys/types.h>
extern "C" {
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netdb.h>
}
#include <ctype.h>
#include <errno.h>

/*
 *  References for IPv6:
 *   http://people.redhat.com/drepper/userapi-ipv6.html
 *   http://www.ietf.org/rfc/rfc2428.txt
 */

bool
InetTransport::callServer(fxStr& emsg)
{
    fxStr service(FAX_SERVICE);
    fxStr proto(client.getProtoName());

    int protocol = 6;	// tcp is protocol number 6, this is a better default than 0
    struct addrinfo hints, *ai;

    if (client.getPort() != -1)
	service = fxStr::format("%d", client.getPort());
    else {
	char* cp;
	if ((cp = getenv("FAXSERVICE")) && *cp != '\0') {
	    fxStr s(cp);
	    u_int l = s.next(0,'/');
	    service = s.head(l);
	    if (l < s.length())
		proto = s.tail(s.length()-(l+1));
	}
    }

    const char* cproto = proto;			// XXX for busted include files
    struct protoent* pp = getprotobyname(cproto);
    if (!pp) {
	client.printWarning("%s: No protocol definition, using default.",
	    cproto);
    } else
	protocol = pp->p_proto;

    memset (&hints, '\0', sizeof (hints));
    hints.ai_flags = AI_CANONNAME;
#ifdef AI_NUMERICHOST
    hints.ai_flags |= AI_NUMERICHOST;
#endif
#ifdef AI_ADDRCONFIG
    hints.ai_flags |= AI_ADDRCONFIG;
#endif
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = protocol;

    int err = getaddrinfo ((const char*) client.getHost(), (const char*) service, &hints, &ai);

#ifdef AI_NUMERICHOST
    if (err == EAI_NONAME) {
	hints.ai_flags &= ~AI_NUMERICHOST;
	err = getaddrinfo (client.getHost(), service, &hints, &ai);
    }
#endif
    if (err != 0) {
	emsg = fxStr::format("getaddrinfo failed with %d: %s", err, gai_strerror(err));
	client.printWarning(emsg);
	return false;
    }

    for (struct addrinfo *aip = ai; aip != NULL; aip = aip->ai_next)
    {
	Socket::Address *addr = (Socket::Address*)aip->ai_addr;
	char buf[256];                          // For inet_ntop use
	fxAssert(aip->ai_family == Socket::family(*addr), "addrinfo ai_family doesn't match in_addr->ai_info");
	if (client.getVerbose())
	    client.traceServer("Trying %s [%d] (%s) at port %u...",
		    (const char*)client.getHost(), Socket::family(*addr),
		    inet_ntop(Socket::family(*addr), Socket::addr(*addr), buf, sizeof(buf)),
		    ntohs(Socket::port(*addr)));
	int fd = socket (aip->ai_family, aip->ai_socktype, aip->ai_protocol);
	if (fd != -1 && ( connect(fd, aip->ai_addr, aip->ai_addrlen) == 0))
	{
	    if (client.getVerbose())
		client.traceServer("Connected to %s.", aip->ai_canonname);
	    /*  Free the addrinfo chain before we leave*/
	    freeaddrinfo(ai);
#if defined(IP_TOS) && defined(IPTOS_LOWDELAY)
	    int tos = IPTOS_LOWDELAY;
	    if (Socket::setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)) < 0)
		client.printWarning("setsockopt(TOS): %s (ignored)",
		    strerror(errno));
#endif
#ifdef SO_OOBINLINE
	    int on = 1;
	    if (Socket::setsockopt(fd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof (on)) < 0)
		client.printWarning("setsockopt(OOBLINE): %s (ignored)",
		    strerror(errno));
#endif
	    /*
	     * NB: We dup the descriptor because some systems
	     * that emulate sockets with TLI incorrectly handle
	     * things if we use the same descriptor for both
	     * input and output (sigh).
	     */
	    client.setCtrlFds(fd, dup(fd));
	    return (true);
	}
	Sys::close(fd), fd = -1;
    }

    emsg = fxStr::format("Can not reach service %s at host \"%s\".",
	(const char*)service, (const char*) client.getHost());
    freeaddrinfo(ai);
    return (false);
}

bool
InetTransport::initDataConn(fxStr& emsg)
{
    struct sockaddr data_addr;
    socklen_t dlen = sizeof (data_addr);

    if (Socket::getsockname(fileno(client.getCtrlFd()), &data_addr, &dlen) < 0) {
	emsg = fxStr::format("getsockname(ctrl): %s", strerror(errno));
	return (false);
    }

    return initDataConnV6(emsg);
}

bool
InetTransport::initDataConnV4(fxStr& emsg)
{
    struct sockaddr_in data_addr;
    socklen_t dlen = sizeof (data_addr);
    if (client.isPassive()) {
	if (client.command("PASV") != FaxClient::COMPLETE)
	    return (false);
	char *cp = (char*) strchr(client.getLastResponse(), '(');
	if (!cp) return (false);
	cp++;
	unsigned int v[6];
	int n = sscanf(cp, "%u,%u,%u,%u,%u,%u", &v[2],&v[3],&v[4],&v[5],&v[0],&v[1]);
	if (n != 6) return (false);
	if (!inet_aton(fxStr::format("%u.%u.%u.%u", v[2],v[3],v[4],v[5]), &data_addr.sin_addr)) {
	    return (false);
	}
	data_addr.sin_port = htons((v[0]<<8)+v[1]);
	data_addr.sin_family = AF_INET;
    } else {
	if (Socket::getsockname(fileno(client.getCtrlFd()), &data_addr, &dlen) < 0) {
	    emsg = fxStr::format("getsockname(ctrl): %s", strerror(errno));
	    return (false);
	}
	data_addr.sin_port = 0;		// let system allocate port
    }
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0) {
	emsg = fxStr::format("socket: %s", strerror(errno));
	return (false);
    }
    if (client.isPassive()) {
	if (Socket::connect(fd, &data_addr, sizeof (data_addr)) >= 0) {
	    if (client.getVerbose())
		client.traceServer("Connected to %s at port %u.", 
		inet_ntoa(data_addr.sin_addr), ntohs(data_addr.sin_port));
	} else {
	    emsg = fxStr::format("Can not reach server at %s at port %u (%s).",
		inet_ntoa(data_addr.sin_addr), ntohs(data_addr.sin_port), strerror(errno));
	    goto bad;
	}
    } else {
	if (Socket::bind(fd, &data_addr, sizeof (data_addr)) < 0) {
	    emsg = fxStr::format("bind: %s", strerror(errno));
	    goto bad;
	}
	dlen = sizeof (data_addr);
	if (Socket::getsockname(fd, &data_addr, &dlen) < 0) {
	    emsg = fxStr::format("getsockname: %s", strerror(errno));
	    goto bad;
	}
	if (listen(fd, 1) < 0) {
	    emsg = fxStr::format("listen: %s", strerror(errno));
	    goto bad;
	}
	const char* a; a = (const char*) &data_addr.sin_addr;	// XXX for __GNUC__
	const char* p; p = (const char*) &data_addr.sin_port;	// XXX for __GNUC__
#define UC(b) (((int) b) & 0xff)
	if (client.command("PORT %u,%u,%u,%u,%u,%u",
	    UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
	    UC(p[0]), UC(p[1])) != FaxClient::COMPLETE)
	    return (false);
#undef UC
    }
    client.setDataFd(fd);
    return (true);
bad:
    Sys::close(fd), fd = -1;
    return (false);
}

bool
InetTransport::initDataConnV6(fxStr& emsg)
{
    /*
     *  IPv6 extensions to FTP
     *    http://www.ietf.org/rfc/rfc2428.txt
     */
    char buf[1024];
    Socket::Address data_addr;
    socklen_t dlen = sizeof(data_addr);


    if (client.isPassive()) {
	if (Socket::getpeername(fileno(client.getCtrlFd()), &data_addr, &dlen) < 0) {
	    emsg = fxStr::format("getsockname(ctrl): %s", strerror(errno));
	    return (false);
	}
	int err = client.command("EPSV");
	if (err == FaxClient::COMPLETE) {
	    u_int s = client.getLastResponse().next(0, '(');
	    u_int e = client.getLastResponse().next(s, ')');
	    if (s++ < e && e < client.getLastResponse().length()) {
		fxStr p = client.getLastResponse().extract(s, e-s);
		char c = p[0];
		if (p[0] == c && p[1] == c && p[2] == c && p[p.length()-1] == c)
		    Socket::port(data_addr) = htons(p.extract(3, p.length()-4));
		else {
		    client.printWarning("Couldn't parse last response \"%s\"", (const char*)client.getLastResponse());
		    return(false);
		}
	    } else {
		client.printWarning("Couldn't parse last response \"%s\"", (const char*)client.getLastResponse());
		return(false);
	    }
	} else if (err == FaxClient::ERROR && Socket::family(data_addr) == AF_INET) {

	    client.printWarning("EPSV not supported, trying PASV since we're AF_INET\n");
	    if (client.command("PASV") != FaxClient::COMPLETE)
		return false;
	    const char *cp = strchr(client.getLastResponse(), '(');
	    if (!cp) {
		client.printWarning("Couldn't parse last response \"%s\"", (const char*)client.getLastResponse());
		return (false);
	    }
	    cp++;
	    unsigned int v[6];
	    int n = sscanf(cp, "%u,%u,%u,%u,%u,%u", &v[2],&v[3],&v[4],&v[5],&v[0],&v[1]);
	    if (n != 6) return (false);
	    if (!inet_aton(fxStr::format("%u.%u.%u.%u", v[2],v[3],v[4],v[5]), &data_addr.in.sin_addr)) {
		return (false);
	    }
	    data_addr.in.sin_port = htons((v[0]<<8)+v[1]);
	    data_addr.in.sin_family = AF_INET;
	    dlen = sizeof(data_addr.in);
	}
    } else {
	if (Socket::getsockname(fileno(client.getCtrlFd()), &data_addr, &dlen) < 0) {
	    emsg = fxStr::format("getsockname(ctrl): %s", strerror(errno));
	    return (false);
	}
	Socket::port(data_addr) = 0;		// let system allocate port
    }

    int fd = socket(Socket::family(data_addr), SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
	emsg = fxStr::format("socket: %s", strerror(errno));
	return (false);
    }
    if (client.isPassive()) {
	if (Socket::connect(fd, &data_addr.in, Socket::socklen(data_addr)) >= 0) {
	    if (client.getVerbose())
		client.traceServer("Connected to %s at port %u.",
		inet_ntop(Socket::family(data_addr), Socket::addr(data_addr), buf, sizeof(buf)), ntohs(Socket::port(data_addr)));
	} else {
	    emsg = fxStr::format("Can not reach server at %s at port %u (%s).",
		inet_ntop(Socket::family(data_addr), Socket::addr(data_addr), buf, sizeof(buf)), ntohs(Socket::port(data_addr)), strerror(errno));
	    goto bad;
	}
    } else {
	if (Socket::bind(fd, &data_addr, dlen) < 0) {
	    emsg = fxStr::format("bind: %s", strerror(errno));
	    goto bad;
	}
	dlen = sizeof (data_addr);
	if (Socket::getsockname(fd, &data_addr, &dlen) < 0) {
	    emsg = fxStr::format("getsockname: %s", strerror(errno));
	    goto bad;
	}
	if (listen(fd, 1) < 0) {
	    emsg = fxStr::format("listen: %s", strerror(errno));
	    goto bad;
	}

	char hostbuf[128];
	char portbuf[64];

	getnameinfo((struct sockaddr*)&data_addr, dlen,
			hostbuf, sizeof(hostbuf), portbuf, sizeof(portbuf),
			NI_NUMERICHOST | NI_NUMERICSERV);
	int err = client.command("EPRT |%d|%s|%s|",
			(Socket::family(data_addr) == AF_INET6 ? 2 : 1),
			hostbuf, portbuf);
	if (err == FaxClient::ERROR && Socket::family(data_addr) == AF_INET)
	{
	    if (client.getVerbose())
		    client.printWarning("EPRT not supported, trying PORT");
	    const char* a; a = (const char*)&data_addr.in.sin_addr;	// XXX for __GNUC__
	    const char* p; p = (const char*)&data_addr.in.sin_port;	// XXX for __GNUC__
#define UC(b) (((int) b) & 0xff)
	    err = client.command("PORT %u,%u,%u,%u,%u,%u",
				UC(a[0]), UC(a[1]), UC(a[2]), UC(a[3]),
				UC(p[0]), UC(p[1]));
#undef UC
	}
	if (err != FaxClient::COMPLETE)
	    return false;
    }

    client.setDataFd(fd);
    return (true);
bad:
    Sys::close(fd), fd = -1;
    return (false);
}


bool
InetTransport::openDataConn(fxStr& emsg)
{
    if (client.isPassive()) {
	return (client.getDataFd() > 0);
    }
    int s = Socket::accept(client.getDataFd(), NULL, NULL);
    if (s >= 0) {
	client.setDataFd(s);
#if defined(IP_TOS) && defined(IPTOS_THROUGHPUT)
	int tos = IPTOS_THROUGHPUT;
	if (Socket::setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof (tos)) < 0)
	    client.printWarning("setsockopt(IP_TOS): %s", strerror(errno));
#endif
	return (true);
    } else {
	emsg = fxStr::format("accept: %s", strerror(errno));
	return (false);
    }
}

/*
 * Send an abort request to terminate an operation.
 * The initial interrupt is sent as urgent data to
 * cause the server to process the subsequent ABOR
 * command immediately.  We send IAC in urgent mode
 * instead of DM because 4.3BSD place the out-of-band
 * mark in the data stream after the urgent byte
 * rather than before as done by most contemporary
 * TCP implementations.
 */
bool
InetTransport::abortCmd(fxStr& emsg)
{
    static const u_char msg[] =
	{ IAC, IP, IAC, DM, 'A', 'B', 'O', 'R', '\r', '\n' };
    int s = fileno(client.getCtrlFd());
    if (send(s, msg, 3, MSG_OOB) != 3) {
	emsg = fxStr::format("send(MSG_OOB): %s", strerror(errno));
	return (false);
    }
    if (send(s, msg+3, sizeof (msg)-3, 0) != sizeof (msg)-3) {
	emsg = fxStr::format("send(<DM>ABOR\\r\\n): %s", strerror(errno));
	return (false);
    }
    return (true);
}
#else
bool InetTransport::callServer(fxStr& emsg)
    { notConfigured("TCP/IP", emsg); return (false); }
bool InetTransport::initDataConn(fxStr& emsg)
    { notConfigured("TCP/IP", emsg); return (false); }
bool InetTransport::openDataConn(fxStr& emsg)
    { notConfigured("TCP/IP", emsg); return (false); }
bool InetTransport::abortDataConn(fxStr& emsg)
    { notConfigured("TCP/IP", emsg); return (false); }
#endif
