+/*************************************************
+* Create connected socket to remote host *
+*************************************************/
+
+/* Create a socket and connect to host (name or number, ipv6 ok)
+ at one of port-range.
+
+Arguments:
+ type SOCK_DGRAM or SOCK_STREAM
+ af AF_INET6 or AF_INET for the socket type
+ hostname host name, or ip address (as text)
+ portlo,porthi the remote port range
+ timeout a timeout
+ connhost if not NULL, host_item to be filled in with connection details
+ errstr pointer for allocated string on error
+ fastopen_blob with SOCK_STREAM, if non-null, request TCP Fast Open.
+ Additionally, optional idempotent early-data to send
+
+Return:
+ socket fd, or -1 on failure (having allocated an error string)
+*/
+int
+ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
+ int timeout, host_item * connhost, uschar ** errstr, const blob * fastopen_blob)
+{
+int namelen;
+host_item shost;
+int af = 0, fd, fd4 = -1, fd6 = -1;
+
+shost.next = NULL;
+shost.address = NULL;
+shost.port = portlo;
+shost.mx = -1;
+
+namelen = Ustrlen(hostname);
+
+/* Anything enclosed in [] must be an IP address. */
+
+if (hostname[0] == '[' &&
+ hostname[namelen - 1] == ']')
+ {
+ uschar * host = string_copyn(hostname+1, namelen-2);
+ if (string_is_ip_address(host, NULL) == 0)
+ {
+ *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
+ return -1;
+ }
+ shost.name = shost.address = host;
+ }
+
+/* Otherwise check for an unadorned IP address */
+
+else if (string_is_ip_address(hostname, NULL) != 0)
+ shost.name = shost.address = string_copyn(hostname, namelen);
+
+/* Otherwise lookup IP address(es) from the name */
+
+else
+ {
+ shost.name = string_copyn(hostname, namelen);
+ if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE,
+ NULL, FALSE) != HOST_FOUND)
+ {
+ *errstr = string_sprintf("no IP address found for host %s", shost.name);
+ return -1;
+ }
+ }
+
+/* Try to connect to the server - test each IP till one works */
+
+for (host_item * h = &shost; h; h = h->next)
+ {
+ fd = Ustrchr(h->address, ':') != 0
+ ? fd6 < 0 ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
+ : fd4 < 0 ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
+
+ if (fd < 0)
+ {
+ *errstr = string_sprintf("failed to create socket: %s", strerror(errno));
+ goto bad;
+ }
+
+ for (int port = portlo; port <= porthi; port++)
+ if (ip_connect(fd, af, h->address, port, timeout, fastopen_blob) == 0)
+ {
+ if (fd6 >= 0 && fd != fd6) close(fd6);
+ if (fd4 >= 0 && fd != fd4) close(fd4);
+ if (connhost)
+ {
+ h->port = port;
+ *connhost = *h;
+ connhost->next = NULL;
+ }
+ return fd;
+ }
+ }
+
+*errstr = string_sprintf("failed to connect to any address for %s: %s",
+ hostname, strerror(errno));
+
+bad:
+ close(fd4); close(fd6); return -1;
+}
+
+
+/*XXX TFO? */
+int
+ip_tcpsocket(const uschar * hostport, uschar ** errstr, int tmo,
+ host_item * connhost)
+{
+int scan;
+uschar hostname[256];
+unsigned int portlow, porthigh;
+
+/* extract host and port part */
+scan = sscanf(CS hostport, "%255s %u-%u", hostname, &portlow, &porthigh);
+if (scan != 3)
+ {
+ if (scan != 2)
+ {
+ *errstr = string_sprintf("invalid socket '%s'", hostport);
+ return -1;
+ }
+ porthigh = portlow;
+ }
+
+return ip_connectedsocket(SOCK_STREAM, hostname, portlow, porthigh,
+ tmo, connhost, errstr, NULL);
+}
+
+int
+ip_unixsocket(const uschar * path, uschar ** errstr)
+{
+int sock;
+struct sockaddr_un server;
+
+if ((sock = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
+ {
+ *errstr = US"can't open UNIX socket.";
+ return -1;
+ }
+
+callout_address = string_copy(path);
+server.sun_family = AF_UNIX;
+Ustrncpy(US server.sun_path, path, sizeof(server.sun_path)-1);
+server.sun_path[sizeof(server.sun_path)-1] = '\0';
+if (connect(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
+ {
+ int err = errno;
+ (void)close(sock);
+ *errstr = string_sprintf("unable to connect to UNIX socket (%s): %s",
+ path, strerror(err));
+ return -1;
+ }
+return sock;
+}
+
+/* spec is either an absolute path (with a leading /), or
+a host (name or IP) and port (whitespace-separated).
+The port can be a range, dash-separated, or a single number.
+
+For a TCP socket, optionally fill in a host_item.
+*/
+int
+ip_streamsocket(const uschar * spec, uschar ** errstr, int tmo,
+ host_item * connhost)
+{
+return *spec == '/'
+ ? ip_unixsocket(spec, errstr) : ip_tcpsocket(spec, errstr, tmo, connhost);
+}
+