]> git.ipfire.org Git - thirdparty/HylaFAX.git/commitdiff
libhylafax: ipv6 support
authorAidan Van Dyk <aidan@ifax.com>
Fri, 16 Jan 2009 18:58:39 +0000 (18:58 +0000)
committerAidan Van Dyk <aidan@ifax.com>
Fri, 16 Jan 2009 18:58:39 +0000 (18:58 +0000)
This brings IPv6 supprt into InetTransport, and adds knowledge of EPRT,
using it where possible to allow connecting IPv6 hylafax servers.

libhylafax/FaxClient.c++
libhylafax/InetTransport.c++
libhylafax/InetTransport.h

index b2ba692c30d428c700c38a21b40a6b49dbc28705..b82ac467cb1b723912a1bda57c865100f20b0238 100644 (file)
@@ -143,6 +143,7 @@ FaxClient::vtraceServer(const char* fmt, va_list ap)
  *
  * e.g. ttyf2@flake.asd:9999.  Alternate forms
  * are: modem@, modem@host, host, host:port.
+ * IPv6 IP addresses (many :) are supported in [xx:xx::x]:port
  */
 void
 FaxClient::setupHostModem(const fxStr& s)
@@ -153,7 +154,18 @@ FaxClient::setupHostModem(const fxStr& s)
        host = s.tail(s.length() - (pos+1));
     } else
        host = s;
-    pos = host.next(0, ':');
+
+    if (host[0] == '[')
+    {
+       host.remove(0,1);
+       pos = host.next(0,']');
+       if (pos == host.length())
+           printWarning(NLS::TEXT("Couldn't parse IPv6 ip address string: \"%s\")"), (const char*)s);
+       else
+       host.remove(pos,1);
+           pos = host.next(pos, ':');
+    } else
+       pos = host.next(0, ':');
     if (pos != host.length()) {
        port = atoi(host.tail(host.length() - (pos+1)));
        host.resize(pos);
index 888724937b1d29f2721877da02f6a6e994f7f587..ff9de2099673c2a3dfafd505403ac685b82809c8 100644 (file)
@@ -54,68 +54,76 @@ extern "C" {
 #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)
 {
-    int port = client.getPort();
+
+    fxStr service(FAX_SERVICE);
     fxStr proto(client.getProtoName());
-    char* cp;
-    if ((cp = getenv("FAXSERVICE")) && *cp != '\0') {
-       fxStr s(cp);
-       u_int l = s.next(0,'/');
-       port = (int) s.head(l);
-       if (l < s.length())
-           proto = s.tail(s.length()-(l+1));
+
+    int protocol = 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));
+       }
     }
 
-    int protocol;
     const char* cproto = proto;                        // XXX for busted include files
     struct protoent* pp = getprotobyname(cproto);
     if (!pp) {
        client.printWarning(NLS::TEXT("%s: No protocol definition, using default."),
            cproto);
-       protocol = 0;
     } else
        protocol = pp->p_proto;
 
-    struct hostent* hp = Socket::gethostbyname(client.getHost());
-    if (!hp) {
-       emsg = client.getHost() | NLS::TEXT(": Unknown host");
-       return (false);
-    }
+    memset (&hints, '\0', sizeof (hints));
+    hints.ai_flags = AI_NUMERICHOST|AI_ADDRCONFIG|AI_CANONNAME;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = protocol;
 
-    int fd = socket(hp->h_addrtype, SOCK_STREAM, protocol);
-    if (fd < 0) {
-       emsg = NLS::TEXT("Can not create socket to connect to server.");
-       return (false);
+    int err = getaddrinfo (client.getHost(), service, &hints, &ai);
+    if (err == EAI_NONAME) {
+       hints.ai_flags &= ~AI_NUMERICHOST;
+       err = getaddrinfo (client.getHost(), service, &hints, &ai);
     }
-    struct sockaddr_in sin;
-    memset(&sin, 0, sizeof (sin));
-    sin.sin_family = hp->h_addrtype;
-    if (port == -1) {
-       struct servent* sp = getservbyname(FAX_SERVICE, cproto);
-       if (!sp) {
-           if (!isdigit(cproto[0])) {
-               client.printWarning(
-                   NLS::TEXT("No \"%s\" service definition, using default %u/%s."),
-                   FAX_SERVICE, FAX_DEFPORT, cproto);
-               sin.sin_port = htons(FAX_DEFPORT);
-           } else
-               sin.sin_port = atoi(cproto);
-       } else
-           sin.sin_port = sp->s_port;
-    } else
-       sin.sin_port = htons(port);
-    for (char** cpp = hp->h_addr_list; *cpp; cpp++) {
-       memcpy(&sin.sin_addr, *cpp, hp->h_length);
+    if (err != 0) {
+       client.printWarning(NLS::TEXT("getaddrinfo failed with %d: %s"), err, gai_strerror(err));
+       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 == addr->family, "addrinfo ai_family doesn't match in_addr->ai_info");
        if (client.getVerbose())
-           client.traceServer(NLS::TEXT("Trying %s (%s) at port %u..."),
-               (const char*) client.getHost(),
-               inet_ntoa(sin.sin_addr),
-               ntohs(sin.sin_port));
-       if (Socket::connect(fd, &sin, sizeof (sin)) >= 0) {
+           client.traceServer(NLS::TEXT("Trying %s [%d] (%s) at port %u..."),
+                   (const char*)client.getHost(), addr->family,
+                   inet_ntop(addr->family, 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(NLS::TEXT("Connected to %s."), hp->h_name);
+               client.traceServer(NLS::TEXT("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)
@@ -137,15 +145,31 @@ InetTransport::callServer(fxStr& emsg)
            client.setCtrlFds(fd, dup(fd));
            return (true);
        }
+       Sys::close(fd), fd = -1;
     }
-    emsg = fxStr::format(NLS::TEXT("Can not reach server at host \"%s\", port %u."),
-       (const char*) client.getHost(), ntohs(sin.sin_port));
-    Sys::close(fd), fd = -1;
+
+    emsg = fxStr::format(NLS::TEXT("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);
@@ -215,6 +239,103 @@ bad:
     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 (client.command("EPSV") != FaxClient::COMPLETE)
+           return (false);
+       const char *cp = 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.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);
+       }
+       data_addr.in.sin_port = 0;              // let system allocate port
+
+    }
+
+    int fd = socket(data_addr.family, 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(data_addr.family, 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(data_addr.family, 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|",
+                       (data_addr.family == AF_INET6 ? 2 : 1),
+                       hostbuf, portbuf);
+       if (err == FaxClient::ERROR && data_addr.family == AF_INET)
+       {
+           client.printWarning(NLS::TEXT("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)
 {
index 2bbcc8562e7129e8fe1009002ce1f9290225679a..4a8b7f85f406748e33aa48a0cf27aff429bf709c 100644 (file)
@@ -42,5 +42,8 @@ public:
 
     bool initDataConn(fxStr& emsg);
     bool openDataConn(fxStr& emsg);
+
+    bool initDataConnV4(fxStr& emsg);
+    bool initDataConnV6(fxStr& emsg);
 };
 #endif /* _InetTransport_ */