#include <sys/socket.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <netdb.h>
#include <net/if.h>
#include <ifaddrs.h>
#include <netsmb/netbios.h>
#include <netsmb/smb_lib.h>
#include <netsmb/nb_lib.h>
#include "charsets.h"
static int SocketUtilsIncrementIfReqIter(UInt8** inIfReqIter, struct ifreq* ifr)
{
*inIfReqIter += sizeof(ifr->ifr_name) + ifr->ifr_addr.sa_len;
if (ifr->ifr_addr.sa_len == 0) {
switch (ifr->ifr_addr.sa_family) {
case AF_INET:
*inIfReqIter += sizeof(struct sockaddr_in);
break;
default:
*inIfReqIter += sizeof(struct sockaddr);
return FALSE;
}
}
return TRUE;
}
static int IsLocalIPv4Address(uint32_t addr)
{
UInt32 kMaxAddrBufferSize = 2048;
UInt8 buffer[kMaxAddrBufferSize];
int so;
UInt8* ifReqIter = NULL;
struct ifconf ifc;
struct ifreq ifreq, *ifr;
int foundit = FALSE;
if (addr == htonl(INADDR_LOOPBACK)) {
return TRUE;
}
if ((so = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
smb_log_info("%s: socket failed, syserr = %s",
ASL_LEVEL_ERR, __FUNCTION__, strerror(errno));
return foundit;
}
ifc.ifc_len = (int)sizeof (buffer);
ifc.ifc_buf = (char*) buffer;
if (ioctl(so, SIOCGIFCONF, (char *)&ifc) < 0) {
smb_log_info("%s: ioctl (get interface configuration), syserr = %s",
ASL_LEVEL_ERR, __FUNCTION__, strerror(errno));
goto WeAreDone;
}
for (ifReqIter = buffer; ifReqIter < (buffer + ifc.ifc_len);) {
ifr = (struct ifreq*)((void *)ifReqIter);
if (!SocketUtilsIncrementIfReqIter(&ifReqIter, ifr)) {
smb_log_info("%s: SocketUtilsIncrementIfReqIter failed!",
ASL_LEVEL_ERR, __FUNCTION__);
break;
}
ifreq = *ifr;
if ((ifr->ifr_addr.sa_family != AF_INET) || (strncmp(ifr->ifr_name, "lo", 2) == 0))
continue;
if (ioctl(so, SIOCGIFFLAGS, (char *)&ifreq) < 0) {
smb_log_info("%s: SIOCGIFFLAGS ioctl failed, syserr = %s",
ASL_LEVEL_ERR, __FUNCTION__, strerror(errno));
continue;
}
if (ifreq.ifr_flags & IFF_UP) {
struct sockaddr_in *laddr = (struct sockaddr_in *)((void *)&(ifreq.ifr_addr));
if ((uint32_t)laddr->sin_addr.s_addr == addr) {
foundit = TRUE;
break;
}
}
}
WeAreDone:
(void) close(so);
return foundit;
}
static int IsLocalIPv6Address ( struct sockaddr_in6 *in6)
{
struct ifaddrs* addr_list, *ifa;
struct sockaddr_in6 *currAddress;
if (IN6_IS_ADDR_LOOPBACK(&in6->sin6_addr)) {
return TRUE;
}
if (getifaddrs(&addr_list)) {
smb_log_info("%s: getifaddrs failed, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, strerror(errno));
return FALSE;
}
for (ifa = addr_list; ifa != NULL; ifa = ifa->ifa_next) {
if (ifa->ifa_addr->sa_family != AF_INET6)
continue;
currAddress = (struct sockaddr_in6 *)((void *)ifa->ifa_addr);
if (IN6_ARE_ADDR_EQUAL (&currAddress->sin6_addr, &in6->sin6_addr))
return TRUE;
}
freeifaddrs(addr_list); ;
return FALSE;
}
int isLocalIPAddress(struct sockaddr *addr, uint16_t port, int allowLocalConn)
{
if (allowLocalConn)
return FALSE;
if ((port != NBSS_TCP_PORT_139) && (port != SMB_TCP_PORT_445))
return FALSE;
if (addr->sa_family == AF_INET) {
struct sockaddr_in *in = (struct sockaddr_in *)((void *)addr);
if (IsLocalIPv4Address(in->sin_addr.s_addr)) {
return TRUE;
}
} else if (addr->sa_family == AF_INET6) {
if (IsLocalIPv6Address((struct sockaddr_in6 *)((void *)addr))) {
return TRUE;
}
} else {
smb_log_info("%s: Unknown address falmily %d?",
ASL_LEVEL_DEBUG, __FUNCTION__, addr->sa_family);
}
return FALSE;
}
int resolvehost(const char *name, CFMutableArrayRef *outAddressArray, char *netbios_name,
uint16_t port, int allowLocalConn, int tryBothPorts)
{
int error;
struct addrinfo hints, *res0, *res;
CFMutableArrayRef addressArray = NULL;
CFMutableDataRef addressData;
if (tryBothPorts && (port == NBSS_TCP_PORT_139))
port = SMB_TCP_PORT_445;
memset (&hints, 0, sizeof (hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
error = getaddrinfo (name, NULL, &hints, &res0);
if (error) {
return (error == EAI_SYSTEM) ? errno : EHOSTUNREACH;
}
addressArray = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
if (!addressArray) {
error = ENOMEM;
goto done;
}
for (res = res0; res; res = res->ai_next) {
struct connectAddress conn;
if ((res->ai_family != PF_INET6) && (res->ai_family != PF_INET)) {
smb_log_info("Skipping address for `%s', unknown address family %d",
ASL_LEVEL_DEBUG, name, res->ai_family);
continue;
}
if (isLocalIPAddress((struct sockaddr *)res->ai_addr, port, allowLocalConn)) {
smb_log_info("The address for `%s' is a loopback address, not allowed!",
ASL_LEVEL_DEBUG, name);
error = ELOOP;
goto done;
}
if ((res->ai_family == PF_INET6) && (port == NBNS_UDP_PORT_137)) {
smb_log_info("Skipping address of `%s', we don't support port 137 on IPV6 addresses",
ASL_LEVEL_DEBUG, name);
continue;
}
if ((res->ai_family == PF_INET6) && (port == NBSS_TCP_PORT_139)) {
smb_log_info("Skipping address of `%s', we don't support port 139 on IPV6 addresses",
ASL_LEVEL_DEBUG, name);
continue;
}
memset(&conn, 0, sizeof(conn));
conn.so = -1;
memcpy(&conn.addr, res->ai_addr, res->ai_addrlen);
if (res->ai_family == PF_INET6) {
conn.in6.sin6_port = htons(port);
} else {
conn.in4.sin_port = htons(port);
}
addressData = CFDataCreateMutable(NULL, 0);
if (addressData) {
if ((port == NBSS_TCP_PORT_139) && (netbios_name))
convertToNetBIOSaddr(&conn.storage, netbios_name);
CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn));
CFArrayAppendValue(addressArray, addressData);
CFRelease(addressData);
}
if (tryBothPorts && (res->ai_family == PF_INET)) {
conn.in4.sin_port = htons(NBSS_TCP_PORT_139);
if (netbios_name)
convertToNetBIOSaddr(&conn.storage, netbios_name);
addressData = CFDataCreateMutable(NULL, 0);
if (addressData) {
CFDataAppendBytes(addressData, (const UInt8 *)&conn, (CFIndex)sizeof(conn));
CFArrayAppendValue(addressArray, addressData);
CFRelease(addressData);
}
}
}
if (CFArrayGetCount(addressArray) == 0) {
error = EHOSTUNREACH;
goto done;
}
done:
freeaddrinfo(res0);
if (error) {
if (addressArray)
CFRelease(addressArray);
addressArray = NULL;
}
*outAddressArray = addressArray;
return error;
}
int isIPv6NumericName(const char *name)
{
int error;
struct addrinfo hints, *res0, *res;
memset (&hints, 0, sizeof (hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_INET6;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
error = getaddrinfo (name, NULL, &hints, &res0);
if (error)
return FALSE;
for (res = res0; res; res = res->ai_next) {
if (res->ai_family == PF_INET6) {
freeaddrinfo(res0);
return TRUE;
}
}
freeaddrinfo(res0);
return FALSE;
}
int
nb_enum_if(struct nb_ifdesc **iflist, int maxif)
{
struct ifconf ifc;
struct ifreq *ifrqp;
struct nb_ifdesc *ifd;
struct in_addr iaddr, imask;
char *ifrdata, *iname;
int s, rdlen, error, iflags, i;
unsigned len;
*iflist = NULL;
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s == -1)
return errno;
rdlen = (int)(maxif * sizeof(struct ifreq));
ifrdata = malloc(rdlen);
if (ifrdata == NULL) {
error = ENOMEM;
goto bad;
}
ifc.ifc_len = rdlen;
ifc.ifc_buf = ifrdata;
if (ioctl(s, SIOCGIFCONF, &ifc) != 0) {
error = errno;
goto bad;
}
ifrqp = ifc.ifc_req;
error = 0;
for (i = 0; i < ifc.ifc_len;
i += len, ifrqp = (struct ifreq *)(void *)((uint8_t *)ifrqp + len)) {
len = (int)_SIZEOF_ADDR_IFREQ(*ifrqp);
if (ifrqp->ifr_addr.sa_family != AF_INET)
continue;
if (ioctl(s, SIOCGIFFLAGS, ifrqp) != 0)
continue;
iflags = ifrqp->ifr_flags;
if ((iflags & IFF_UP) == 0 || (iflags & IFF_BROADCAST) == 0)
continue;
if (ioctl(s, SIOCGIFADDR, ifrqp) != 0 ||
ifrqp->ifr_addr.sa_family != AF_INET)
continue;
iname = ifrqp->ifr_name;
if (strlen(iname) >= sizeof(ifd->id_name))
continue;
iaddr = (*(struct sockaddr_in *)(void *)&ifrqp->ifr_addr).sin_addr;
if (ioctl(s, SIOCGIFNETMASK, ifrqp) != 0)
continue;
imask = ((struct sockaddr_in *)(void *)&ifrqp->ifr_addr)->sin_addr;
ifd = malloc(sizeof(struct nb_ifdesc));
if (ifd == NULL)
return ENOMEM;
bzero(ifd, sizeof(struct nb_ifdesc));
strlcpy(ifd->id_name, iname, sizeof(ifd->id_name));
ifd->id_flags = iflags;
ifd->id_addr = iaddr;
ifd->id_mask = imask;
ifd->id_next = *iflist;
*iflist = ifd;
}
bad:
if (ifrdata)
free(ifrdata);
close(s);
return error;
}
#define kPollSeconds 5
#define kMaxTimeToWait 60
static int nonBlockingSocket(int family)
{
int so, flags;
so = socket(family, SOCK_STREAM, 0);
if (so < 0) {
smb_log_info("%s: socket call failed for family %d, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, family, strerror(errno));
return -1;
}
if ( (flags = fcntl(so, F_GETFL, NULL)) < 0 ) {
smb_log_info("%s: F_GETFL call failed for family %d, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, family, strerror(errno));
close(so);
return -1;
}
flags |= O_NONBLOCK;
if ( fcntl(so, F_SETFL, flags) < 0 ) {
smb_log_info("%s: F_SETFL call failed for sa_family %d, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, family, strerror(errno));
close(so);
return -1;
}
return so;
}
int findReachableAddress(CFMutableArrayRef addressArray, uint16_t *cancel, struct connectAddress **dest)
{
struct timeval tv;
int error = 0;
fd_set writefds;
int nfds = 0;
CFIndex ii, numAddresses = CFArrayGetCount(addressArray);
int32_t totalWaitTime = 0;
CFMutableDataRef dataRef;
struct connectAddress *conn;
*dest = NULL;
FD_ZERO(&writefds);
for (ii = 0; ii < numAddresses; ii++) {
dataRef = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, ii);
if (!dataRef)
continue;
conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(dataRef));
if (!conn)
continue;
if ( (cancel) && (*cancel == TRUE) ) {
smb_log_info("%s: Connection cancelled", ASL_LEVEL_DEBUG, __FUNCTION__);
error = ECANCELED;
goto done;
}
if (conn->addr.sa_family == AF_NETBIOS)
conn->so = nonBlockingSocket(AF_INET);
else
conn->so = nonBlockingSocket(conn->addr.sa_family);
if (conn->so < 0) {
continue;
}
if (conn->addr.sa_family == AF_NETBIOS)
error = connect(conn->so, (struct sockaddr *)&conn->nb.snb_addrin, conn->nb.snb_addrin.sin_len);
else
error = connect(conn->so, &conn->addr, conn->addr.sa_len);
if (error < 0) {
if (errno == EINPROGRESS) {
FD_SET(conn->so, &writefds);
if (conn->so > nfds)
nfds = conn->so;
} else {
smb_log_info("%s: Connection %ld failed, family = %d, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, ii,
conn->addr.sa_family, strerror(errno));
close (conn->so);
conn->so = -1;
}
continue;
}
*dest = conn;
goto done;
}
while (nfds && (totalWaitTime < kMaxTimeToWait)) {
tv.tv_sec = kPollSeconds;
tv.tv_usec = 0;
error = select(nfds + 1, NULL, &writefds, NULL, &tv);
if (error < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) {
error = 0;
} else {
error = errno;
smb_log_info("%s: Select call failed, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
goto done;
}
}
if (error > 0) {
nfds = 0;
error = 0;
for (ii = 0; ii < numAddresses; ii++) {
socklen_t dummy;
dataRef = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, ii);
if (!dataRef)
continue;
conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(dataRef));
if (!conn)
continue;
if (conn->so < 0)
continue;
if (FD_ISSET(conn->so, &writefds) == 0) {
FD_SET (conn->so, &writefds);
if (conn->so > nfds)
nfds = conn->so;
continue;
}
dummy = sizeof(int);
if (getsockopt(conn->so, SOL_SOCKET, SO_ERROR, (void*)(&error), &dummy) < 0) {
error = errno;
smb_log_info("%s: getsockopt failed, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, strerror(errno));
}
if (error) {
if (error != EINPROGRESS) {
smb_log_info("%s: Connection failed, syserr = %s",
ASL_LEVEL_DEBUG, __FUNCTION__, strerror(error));
FD_CLR (conn->so, &writefds);
close (conn->so);
conn->so = -1;
} else {
FD_SET (conn->so, &writefds);
if (conn->so > nfds)
nfds = conn->so;
}
error = 0;
continue;
}
*dest = conn;
goto done;
}
} else {
totalWaitTime += kPollSeconds;
if ( (cancel) && (*cancel == TRUE) ) {
smb_log_info("%s: Connection cancelled", ASL_LEVEL_DEBUG, __FUNCTION__);
error = ECANCELED;
goto done;
}
nfds = 0;
for (ii = 0; ii < numAddresses; ii++) {
dataRef = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, ii);
if (!dataRef)
continue;
conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(dataRef));
if (!conn)
continue;
if (conn->so < 0)
continue;
if (FD_ISSET(conn->so, &writefds) == 0) {
FD_SET (conn->so, &writefds);
if (conn->so > nfds)
nfds = conn->so;
}
}
}
}
done:
if (!error && (*dest == NULL))
error = ETIMEDOUT;
for (ii = 0; ii < numAddresses; ii++) {
dataRef = (CFMutableDataRef)CFArrayGetValueAtIndex(addressArray, ii);
if (!dataRef)
continue;
conn = (struct connectAddress *)((void *)CFDataGetMutableBytePtr(dataRef));
if (!conn)
continue;
if (conn->so != -1)
close (conn->so);
}
return error;
}