A few weeks ago I'd submitted a patch (to the newsgroup) to fix an
IPv6/SSL compatibility issue in tcp_unix.c.  Since then I've worked on
getting rid of other nuisance problems with handling IPv6 connections.
Most notably, imapd wouldn't syslog IPv6 addresses properly - instead it
would log a 'NON-IPv4' message.  And pine could not make IPv6 client
connections.  The attached patch to tcp_unix.c fixes those problems and
incorporates the earlier IPv6/SSL bugfix.  It also includes some
miscellaneous code cleanup.  Here's a summary of the changes:

- IPv6/SSL compatibility

  The server port was not being initialized if the connection was over
  IPv6.  Corrected tcp_serverhost() to handle that case.

- Logging IPv6 addresses in syslog messages

  Calls to inet_ntoa() have been replaced by a protocol-independent
  function socktop() which calls inet_ntop().

- Client IPv6 connections

  The first half of tcp_open() used a combination of inet_addr() and
  gethostbyname() to resolve the user's host argument into an IP address
  and ultimately into a socket address structure.  This section was
  simplified using getaddrinfo() and the IP version dependency is now
  hidden within addrinfo structures.  tcp_socket_open() is now passed a
  reference to a generic sockaddr structure instead of the IPv4 dependent
  sockaddr_in.

- Buffer overflow prevention

  sprintf() and strcpy() are replaced by snprintf() and strncpy() in
  the cases where the destination buffer size is known.

This should basically make pine and imapd completely IPv6 capable.  The
patch has been tested with the FreeBSD ports of imapd, c-client, and pine.

Depending on how much backward compatibility is desired for older UNIX
operating systems which do not have IPv6 stacks, integrating this patch
into the c-client distribution might take some extra work.  I don't have
ready access to older IPv4-only UNIX systems anymore so the patch has no
'#ifdef IPV6' tests at all.  However someone else is free to sprinkle
those in where appropriate.  An alternative to doing that might be to use
this patch to create a tcp6_unix.c, define different Makefile targets for
the IPv4-only vs IPv6 UNIX systems, and include tcp6_unix.c in the
appropriate build targets.
--- src/osdep/unix/tcp_unix.c.orig      Thu Oct 31 10:04:41 2002
+++ src/osdep/unix/tcp_unix.c   Tue Dec  9 09:37:14 2003
@@ -37,11 +37,12 @@
 
 /* Local function prototypes */
 
-int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst,
+int tcp_socket_open( struct sockaddr *sa, char *tmp, int *ctr, char *hst,
                     unsigned long port);
 long tcp_abort (TCPSTREAM *stream);
-char *tcp_name (struct sockaddr_in *sin,long flag);
+char *tcp_name( struct sockaddr *sa, long flag );
 long tcp_name_valid (char *s);
+char *socktop( struct sockaddr *sa ); 
 
 /* TCP/IP manipulate parameters
  * Accepts: function code
@@ -138,7 +139,7 @@
   int silent = (port & NET_SILENT) ? T : NIL;
   int *ctrp = (port & NET_NOOPENTIMEOUT) ? NIL : &ctr;
   char *s;
-  struct sockaddr_in sin;
+  struct addrinfo hints, *result;
   struct hostent *he;
   char hostname[MAILTMPLEN];
   char tmp[MAILTMPLEN];
@@ -147,62 +148,44 @@
   void *data;
   port &= 0xffff;              /* erase flags */
                                /* lookup service */
+  memset( &hints, 0, sizeof hints );
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+
   if (service && (sv = getservbyname (service,"tcp")))
-    port = ntohs (sin.sin_port = sv->s_port);
-                               /* copy port number in network format */
-  else sin.sin_port = htons (port);
+    port = ntohs( sv->s_port );
+  
+  if (tcpdebug) {
+    snprintf( tmp, sizeof tmp, "DNS resolution %.80s", host );
+    mm_log (tmp,TCPDEBUG);
+  }
+
   /* The domain literal form is used (rather than simply the dotted decimal
      as with other Unix programs) because it has to be a valid "host name"
      in mailsystem terminology. */
                                /* look like domain literal? */
-  if (host[0] == '[' && host[(strlen (host))-1] == ']') {
-    strcpy (hostname,host+1);  /* yes, copy number part */
-    hostname[(strlen (hostname))-1] = '\0';
-    if ((sin.sin_addr.s_addr = inet_addr (hostname)) == -1)
-      sprintf (tmp,"Bad format domain-literal: %.80s",host);
-    else {
-      sin.sin_family = AF_INET;        /* family is always Internet */
-      strcpy (hostname,host);  /* hostname is user's argument */
-      (*bn) (BLOCK_TCPOPEN,NIL);
-                               /* get an open socket for this system */
-      sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
-      (*bn) (BLOCK_NONE,NIL);
-    }
-  }
+  if (host[0] == '[' && host[(strlen (host))-1] == ']')
+    strncpy( hostname, host+1, sizeof hostname ); /* yes, copy number part */
+  else strncpy( hostname, lcase( host ), sizeof hostname );
 
-  else {                       /* lookup host name */
-    if (tcpdebug) {
-      sprintf (tmp,"DNS resolution %.80s",host);
-      mm_log (tmp,TCPDEBUG);
-    }
     (*bn) (BLOCK_DNSLOOKUP,NIL);/* quell alarms */
     data = (*bn) (BLOCK_SENSITIVE,NIL);
-    if (!(he = gethostbyname (lcase (strcpy (hostname,host)))))
-      sprintf (tmp,"No such host as %.80s",host);
+  i = getaddrinfo( hostname, service, &hints, &result );
     (*bn) (BLOCK_NONSENSITIVE,data);
     (*bn) (BLOCK_NONE,NIL);
-    if (he) {                  /* DNS resolution won? */
-      if (tcpdebug) mm_log ("DNS resolution done",TCPDEBUG);
-                               /* copy address type */
-      sin.sin_family = he->h_addrtype;
-                               /* copy host name */
-      strcpy (hostname,he->h_name);
-#ifdef HOST_NOT_FOUND          /* muliple addresses only on DNS systems */
-      for (sock = -1,i = 0; (sock < 0) && (s = he->h_addr_list[i]); i++) {
-       if (i && !silent) mm_log (tmp,WARN);
-       memcpy (&sin.sin_addr,s,he->h_length);
-       (*bn) (BLOCK_TCPOPEN,NIL);
-       sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
-       (*bn) (BLOCK_NONE,NIL);
-      }
-#else                          /* the one true address then */
-      memcpy (&sin.sin_addr,he->h_addr,he->h_length);
+
+  if ( i ) snprintf( tmp, sizeof tmp, "No such host as %.80s", host);
+  else do {
+
       (*bn) (BLOCK_TCPOPEN,NIL);
-      sock = tcp_socket_open (&sin,tmp,ctrp,hostname,port);
+                               /* get an open socket for this system */
+    sock = tcp_socket_open( result->ai_addr, tmp, ctrp, hostname, port );
       (*bn) (BLOCK_NONE,NIL);
-#endif
-    }
-  }
+
+    if ( sock >= 0 ) break;
+
+  } while ( ( result = result->ai_next ) != NULL );
+
   if (sock >= 0)  {            /* won */
     stream = (TCPSTREAM *) memset (fs_get (sizeof (TCPSTREAM)),0,
                                   sizeof (TCPSTREAM));
@@ -228,21 +211,23 @@
  * Returns: socket if success, else -1 with error string in scratch buffer
  */
 
-int tcp_socket_open (struct sockaddr_in *sin,char *tmp,int *ctr,char *hst,
-                    unsigned long port)
+int tcp_socket_open( struct sockaddr *sa, char *tmp, int *ctr,
+                     char *hst, unsigned long port)
 {
   int i,ti,sock,flgs;
   time_t now;
   struct protoent *pt = getprotobyname ("tcp");
   fd_set fds,efds;
   struct timeval tmo;
+  socklen_t salen;
   blocknotify_t bn = (blocknotify_t) mail_parameters (NIL,GET_BLOCKNOTIFY,NIL);
                                /* fetid Solaris */
   void *data = (*bn) (BLOCK_SENSITIVE,NIL);
-  sprintf (tmp,"Trying IP address [%s]",inet_ntoa (sin->sin_addr));
+  sprintf( tmp, "Trying IP address [%s]", socktop( sa ) );
   mm_log (tmp,NIL);
                                /* make a socket */
-  if ((sock = socket (sin->sin_family,SOCK_STREAM,pt ? pt->p_proto : 0)) < 0) {
+  if ( ( sock = socket( sa->sa_family, SOCK_STREAM, pt ? pt->p_proto : 0 ) )
+       < 0 ) {
     sprintf (tmp,"Unable to create TCP socket: %s",strerror (errno));
     (*bn) (BLOCK_NONSENSITIVE,data);
     return -1;
@@ -251,8 +236,11 @@
                                /* set non-blocking if want open timeout */
   if (ctr) fcntl (sock,F_SETFL,flgs | FNDELAY);
                                /* open connection */
-  while ((i = connect (sock,(struct sockaddr *) sin,
-                      sizeof (struct sockaddr_in))) < 0 && (errno == EINTR));
+
+  if ( sa->sa_family == AF_INET ) salen = sizeof (struct sockaddr_in);
+  else salen = sizeof (struct sockaddr_in6);
+  while ( ( i = connect( sock, (struct sockaddr *) sa, salen ) ) < 0 &&
+          ( errno == EINTR ) );
   (*bn) (BLOCK_NONSENSITIVE,data);
   if (i < 0) switch (errno) {  /* failed? */
   case EAGAIN:                 /* DG brain damage */
@@ -333,10 +321,10 @@
   else return NIL;             /* rsh disabled */
                                /* look like domain literal? */
   if (mb->host[0] == '[' && mb->host[i = (strlen (mb->host))-1] == ']') {
-    strcpy (host,mb->host+1);  /* yes, copy without brackets */
-    host[i-1] = '\0';
-    if (inet_addr (host) == -1) {
-      sprintf (tmp,"Bad format domain-literal: %.80s",host);
+    strncpy( host, mb->host+1, sizeof host );  /* yes, copy without brackets */
+    if ( ( inet_pton( AF_INET, host, tmp ) <= 0 ) &&
+         ( inet_pton( AF_INET6, host, tmp ) <= 0 ) ) {
+      snprintf( tmp, sizeof tmp, "Bad format domain-literal: %.80s", host );
       mm_log (tmp,ERROR);
       return NIL;
     }
@@ -345,24 +333,25 @@
     (*bn) (BLOCK_DNSLOOKUP,NIL);
     data = (*bn) (BLOCK_SENSITIVE,NIL);
     if (tcpdebug) {
-      sprintf (tmp,"DNS canonicalization for rsh/ssh %.80s",mb->host);
+      snprintf( tmp, sizeof tmp, "DNS canonicalization for rsh/ssh %.80s",
+                mb->host);
       mm_log (tmp,TCPDEBUG);
     }
-    if (he = gethostbyname (lcase (strcpy (host,mb->host))))
-      strcpy (host,he->h_name);
+    if ( he = gethostbyname( lcase( strncpy( host, mb->host, sizeof host ) ) ) )
+      strncpy( host, he->h_name, sizeof host );
     if (tcpdebug) mm_log ("DNS canonicalization for rsh/ssh done",TCPDEBUG);
     (*bn) (BLOCK_NONSENSITIVE,data);
     (*bn) (BLOCK_NONE,NIL);
   }
 
   if (*service == '*')         /* build ssh command */
-    sprintf (tmp,sshcommand,sshpath,host,
+    snprintf( tmp, sizeof tmp, sshcommand, sshpath, host,
             mb->user[0] ? mb->user : myusername (),service + 1);
-  else sprintf (tmp,rshcommand,rshpath,host,
+  else snprintf( tmp, sizeof tmp, rshcommand, rshpath, host,
                mb->user[0] ? mb->user : myusername (),service);
   if (tcpdebug) {
     char msg[MAILTMPLEN];
-    sprintf (msg,"Trying %.100s",tmp);
+    snprintf( msg, sizeof msg, "Trying %.100s",tmp );
     mm_log (msg,TCPDEBUG);
   }
                                /* parse command into argv */
@@ -422,8 +411,9 @@
     if ((i < 0) && (errno == EINTR) && ti && (ti <= now)) i = 0;
   } while ((i < 0) && (errno == EINTR));
   if (i <= 0) {                        /* timeout or error? */
-    sprintf (tmp,i ? "error in %s to IMAP server" :
-            "%s to IMAP server timed out",(*service == '*') ? "ssh" : "rsh");
+    snprintf( tmp, sizeof tmp, i ? "error in %s to IMAP server" :
+                                   "%s to IMAP server timed out",
+              (*service == '*') ? "ssh" : "rsh");
     mm_log (tmp,WARN);
     tcp_close (stream);                /* punt stream */
     stream = NIL;
@@ -700,12 +690,13 @@
 char *tcp_remotehost (TCPSTREAM *stream)
 {
   if (!stream->remotehost) {
-    struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+    struct sockaddr_storage sa;
+    socklen_t salen = sizeof sa;
+
     stream->remotehost =       /* get socket's peer name */
-      (getpeername (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) ||
-       (sin.sin_family != AF_INET)) ?
-        cpystr (stream->host) : tcp_name (&sin,NIL);
+      ( getpeername( stream->tcpsi, (struct sockaddr *) &sa, &salen ) ||
+        ( sa.ss_family != AF_INET && sa.ss_family != AF_INET6 ) ) ?
+        cpystr( stream->host ) : tcp_name( (struct sockaddr *) &sa, NIL );
   }
   return stream->remotehost;
 }
@@ -730,13 +721,14 @@
 char *tcp_localhost (TCPSTREAM *stream)
 {
   if (!stream->localhost) {
-    struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+    struct sockaddr_storage sa;
+    socklen_t salen = sizeof sa;
+
     stream->localhost =                /* get socket's name */
       ((stream->port & 0xffff000) ||
-       getsockname (stream->tcpsi,(struct sockaddr *) &sin,(void *) &sinlen) ||
-       (sin.sin_family != AF_INET)) ?
-        cpystr (mylocalhost ()) : tcp_name (&sin,NIL);
+        getsockname( stream->tcpsi, (struct sockaddr *) &sa, &salen ) ||
+        ( sa.ss_family != AF_INET && sa.ss_family != AF_INET6 ) ) ?
+        cpystr( mylocalhost () ) : tcp_name( (struct sockaddr *) &sa, NIL );
   }
   return stream->localhost;    /* return local host name */
 }
@@ -750,12 +742,14 @@
 char *tcp_clientaddr ()
 {
   if (!myClientAddr) {
-    struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+    struct sockaddr_storage sa;
+    socklen_t salen = sizeof sa;
+
     myClientAddr =             /* get stdin's peer name */
-      cpystr (getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
-             "UNKNOWN" : ((sin.sin_family == AF_INET) ?
-                          inet_ntoa (sin.sin_addr) : "NON-IPv4"));
+      cpystr( getpeername( 0, (struct sockaddr *) &sa, &salen ) ?
+              "UNKNOWN" : ( ( sa.ss_family == AF_INET ||
+                              sa.ss_family == AF_INET6 ) ?
+                            socktop ( (struct sockaddr *) &sa ) : "NON-IP" ) );
   }
   return myClientAddr;
 }
@@ -770,12 +764,15 @@
 char *tcp_clienthost ()
 {
   if (!myClientHost) {
-    struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+    struct sockaddr_storage sa;
+    socklen_t salen = sizeof sa;
+
     myClientHost =             /* get stdin's peer name */
-      getpeername (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
-       cpystr ("UNKNOWN") : ((sin.sin_family == AF_INET) ?
-                             tcp_name (&sin,T) : cpystr ("NON-IPv4"));
+      getpeername( 0, (struct sockaddr *) &sa, &salen ) ?
+        cpystr("UNKNOWN") : ( ( sa.ss_family == AF_INET ||
+                                sa.ss_family == AF_INET6 ) ?
+                              tcp_name ( (struct sockaddr *) &sa, T ) :
+                              cpystr( "NON-IP" ) );
   }
   return myClientHost;
 }
@@ -789,12 +786,14 @@
 char *tcp_serveraddr ()
 {
   if (!myServerAddr) {
-    struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+    struct sockaddr_storage sa;
+    socklen_t salen = sizeof sa;
+
     myServerAddr =             /* get stdin's peer name */
-      cpystr (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) ?
-             "UNKNOWN" : ((sin.sin_family == AF_INET) ?
-                          inet_ntoa (sin.sin_addr) : "NON-IPv4"));
+      cpystr( getsockname( 0, (struct sockaddr *) &sa, &salen ) ?
+              "UNKNOWN" : ( ( sa.ss_family == AF_INET ||
+                              sa.ss_family == AF_INET6 ) ?
+                            socktop( (struct sockaddr *) &sa ) : "NON-IP" ) );
   }
   return myServerAddr;
 }
@@ -810,14 +809,26 @@
 char *tcp_serverhost ()
 {
   if (!myServerHost) {
+    union {
+      struct sockaddr_storage ss;
     struct sockaddr_in sin;
-    int sinlen = sizeof (struct sockaddr_in);
+      struct sockaddr_in6 sin6;
+    } sa;
+    socklen_t salen = sizeof sa;
+
                                /* get stdin's name */
-    if (getsockname (0,(struct sockaddr *) &sin,(void *) &sinlen) ||
-       (sin.sin_family != AF_INET)) myServerHost = cpystr (mylocalhost ());
+    if ( getsockname( 0, (struct sockaddr *) &sa, &salen ) )
+      myServerHost = cpystr( mylocalhost() );
     else {
-      myServerHost = tcp_name (&sin,NIL);
-      myServerPort = ntohs (sin.sin_port);
+      if ( sa.ss.ss_family == AF_INET ) {
+        myServerHost = tcp_name( (struct sockaddr *) &sa, T );
+        myServerPort = ntohs( sa.sin.sin_port );
+      }
+      else if ( sa.ss.ss_family == AF_INET6 ) {
+        myServerHost = tcp_name( (struct sockaddr *) &sa, T );
+        myServerPort = ntohs( sa.sin6.sin6_port );
+      }
+      else myServerHost = cpystr( mylocalhost() );
     }
   }
   return myServerHost;
@@ -850,11 +861,11 @@
   (*bn) (BLOCK_DNSLOOKUP,NIL); /* quell alarms */
   data = (*bn) (BLOCK_SENSITIVE,NIL);
   if (tcpdebug) {
-    sprintf (host,"DNS canonicalization %.80s",name);
+    snprintf( host, sizeof host, "DNS canonicalization %.80s", name );
     mm_log (host,TCPDEBUG);
   }
                                /* note that Unix requires lowercase! */
-  ret = (he = gethostbyname (lcase (strcpy (host,name)))) ?
+  ret = ( he = gethostbyname( lcase( strncpy( host, name, sizeof host ) ) ) ) ?
     (char *) he->h_name : name;
   (*bn) (BLOCK_NONSENSITIVE,data);
   (*bn) (BLOCK_NONE,NIL);      /* alarms OK now */
@@ -869,32 +880,53 @@
  * Returns: cpystr name
  */
 
-char *tcp_name (struct sockaddr_in *sin,long flag)
+char *tcp_name( struct sockaddr *sa, long flag )
 {
   char *s,tmp[MAILTMPLEN];
+
   if (allowreversedns) {
     struct hostent *he;
     blocknotify_t bn = (blocknotify_t)mail_parameters(NIL,GET_BLOCKNOTIFY,NIL);
     void *data;
+
     if (tcpdebug) {
-      sprintf (tmp,"Reverse DNS resolution [%s]",inet_ntoa (sin->sin_addr));
+      snprintf( tmp, sizeof tmp, "Reverse DNS resolution [%s]",
+                socktop( sa ) );
       mm_log (tmp,TCPDEBUG);
     }
+
     (*bn) (BLOCK_DNSLOOKUP,NIL); /* quell alarms */
     data = (*bn) (BLOCK_SENSITIVE,NIL);
+
                                /* translate address to name */
-    if (!(he = gethostbyaddr ((char *) &sin->sin_addr,
-                             sizeof (struct in_addr),sin->sin_family)) ||
-       !tcp_name_valid ((char *) he->h_name))
-      sprintf (s = tmp,"[%s]",inet_ntoa (sin->sin_addr));
-    else if (flag) sprintf (s = tmp,"%s [%s]",he->h_name,
-                           inet_ntoa (sin->sin_addr));
+    if ( sa->sa_family == AF_INET ) {
+      struct sockaddr_in *sin = (void *) sa;
+      he = gethostbyaddr( (char *) &sin->sin_addr,
+                          sizeof (struct in_addr), AF_INET );
+    }
+    else if ( sa->sa_family == AF_INET6 ) {
+      struct sockaddr_in6 *sin6 = (void *) sa;
+      he = gethostbyaddr( (char *) &sin6->sin6_addr,
+                          sizeof (struct in6_addr), AF_INET6 );
+    }
+    else he = NULL;
+
+    if ( ! he || ! tcp_name_valid( (char *) he->h_name ) )
+      snprintf( s = tmp, sizeof tmp, "[%s]", socktop( sa ) );
+
+    else if (flag) snprintf( s = tmp, sizeof tmp, "%s [%s]", he->h_name,
+                             socktop( sa ) );
+
     else s = (char *) he->h_name;
+
     (*bn) (BLOCK_NONSENSITIVE,data);
     (*bn) (BLOCK_NONE,NIL);    /* alarms OK now */
+
     if (tcpdebug) mm_log ("Reverse DNS resolution done",TCPDEBUG);
   }
-  else sprintf (s = tmp,"[%s]",inet_ntoa (sin->sin_addr));
+
+  else snprintf( s = tmp, sizeof tmp, "[%s]", socktop( sa ) );
+
   return cpystr (s);
 }
 
@@ -907,8 +939,29 @@
 long tcp_name_valid (char *s)
 {
   int c;
-  while (c = *s++)             /* must be alnum, dot, or hyphen */
+  while (c = *s++)             /* must be alnum, dot, colon, or hyphen */
     if (!((c >= 'A') && (c <= 'Z')) && !((c >= 'a') && (c <= 'z')) &&
-       !((c >= '0') && (c <= '9')) && (c != '-') && (c != '.')) return NIL;
+       !((c >= '0') && (c <= '9')) && (c != '-') && (c != '.') &&
+        (c != ':')) return NIL;
   return LONGT;
+}
+
+/* Get a printable address from a socket address structure.
+ * Returns a pointer to the printable address or NULL.
+ */
+
+char *socktop( struct sockaddr *sa ) {
+  static char str[INET6_ADDRSTRLEN];
+  socklen_t strlen = sizeof str;
+
+  if ( sa->sa_family == AF_INET ) {
+    struct sockaddr_in *sin = (void *) sa;
+    return (char *) inet_ntop( AF_INET, &sin->sin_addr, str, strlen );
+  }
+  else if ( sa->sa_family == AF_INET6 ) {
+    struct sockaddr_in6 *sin6 = (void *) sa;
+    return (char *) inet_ntop( AF_INET6, &sin6->sin6_addr, str, strlen );
+  }
+  return NULL;
+
 }

Reply via email to