/* ** OSSP sa - Socket Abstraction ** Copyright (c) 2001-2002 Ralf S. Engelschall ** Copyright (c) 2001-2002 The OSSP Project ** Copyright (c) 2001-2002 Cable & Wireless Deutschland ** ** This file is part of OSSP sa, a socket abstraction library which ** can be found at http://www.ossp.org/pkg/lib/sa/. ** ** Permission to use, copy, modify, and distribute this software for ** any purpose with or without fee is hereby granted, provided that ** the above copyright notice and this permission notice appear in all ** copies. ** ** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED ** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF ** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ** SUCH DAMAGE. ** ** sa.c: socket abstraction library */ /* include optional Autoconf header */ #include "l2_config.h" /* include system API headers */ #include /* for "s[n]printf()" */ #include /* for "malloc()" & friends */ #include /* for "va_XXX()" and "va_list" */ #include /* for "strXXX()" and "size_t" */ #include /* for general prerequisites */ #include /* for isXXX() */ #include /* for "EXXX" */ #include /* for "F_XXX" and "O_XXX" */ #include /* for standard Unix stuff */ #include /* for "struct prototent" */ #include /* for "struct timeval" */ #include /* for "struct sockaddr_un" */ #include /* for "struct sockaddr_in[6]" */ #include /* for "PF_XXX", "AF_XXX", "SOCK_XXX" and "SHUT_XX" */ #include /* for "inet_XtoX" */ /* include own API header */ #include "l2_ut_sa.h" /* unique library identifier */ const char sa_id[] = "OSSP sa"; /* support for OSSP ex based exception throwing */ #ifdef WITH_EX #include "ex.h" #define SA_RC(rv) \ ( (rv) != SA_OK && (ex_catching && !ex_shielding) \ ? (ex_throw(sa_id, NULL, (rv)), (rv)) : (rv) ) #else #define SA_RC(rv) (rv) #endif /* WITH_EX */ /* boolean values */ #ifndef FALSE #define FALSE (0) #endif #ifndef TRUE #define TRUE (!FALSE) #endif /* backward compatibility for AF_LOCAL */ #if !defined(AF_LOCAL) && defined(AF_UNIX) #define AF_LOCAL AF_UNIX #endif /* backward compatibility for PF_XXX (still unused) */ #if !defined(PF_LOCAL) && defined(AF_LOCAL) #define PF_LOCAL AF_LOCAL #endif #if !defined(PF_INET) && defined(AF_INET) #define PF_INET AF_INET #endif #if !defined(PF_INET6) && defined(AF_INET6) #define PF_INET6 AF_INET6 #endif /* backward compatibility for SHUT_XX. Some platforms (like brain-dead OpenUNIX) define those only if _XOPEN_SOURCE is defined, but unfortunately then fail in their other vendor includes due to internal inconsistencies. */ #if !defined(SHUT_RD) #define SHUT_RD 0 #endif #if !defined(SHUT_WR) #define SHUT_WR 1 #endif #if !defined(SHUT_RDWR) #define SHUT_RDWR 2 #endif /* backward compatibility for ssize_t */ #if !defined(HAVE_SSIZE_T) #define ssize_t long #endif /* backward compatibility for O_NONBLOCK */ #if !defined(O_NONBLOCK) && defined(O_NDELAY) #define O_NONBLOCK O_NDELAY #endif /* system call structure declaration macros */ #define SA_SC_DECLARE_0(rc_t, fn) \ struct { \ union { void (*any)(); \ rc_t (*std)(void); \ rc_t (*ctx)(void *); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_1(rc_t, fn, a1_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t); \ rc_t (*ctx)(void *, a1_t); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_2(rc_t, fn, a1_t, a2_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t, a2_t); \ rc_t (*ctx)(void *, a1_t, a2_t); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_3(rc_t, fn, a1_t, a2_t, a3_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t, a2_t, a3_t); \ rc_t (*ctx)(void *, a1_t, a2_t, a3_t); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_4(rc_t, fn, a1_t, a2_t, a3_t, a4_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t, a2_t, a3_t, a4_t); \ rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_5(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t); \ rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t); } fptr; \ void *fctx; \ } sc_##fn; #define SA_SC_DECLARE_6(rc_t, fn, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t) \ struct { \ union { void (*any)(); \ rc_t (*std)(a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); \ rc_t (*ctx)(void *, a1_t, a2_t, a3_t, a4_t, a5_t, a6_t); } fptr; \ void *fctx; \ } sc_##fn; /* system call structure assignment macro */ #define SA_SC_ASSIGN(sa, fn, ptr, ctx) \ do { \ (sa)->scSysCall.sc_##fn.fptr.any = (void (*)())(ptr); \ (sa)->scSysCall.sc_##fn.fctx = (ctx); \ } while (0) /* system call structure assignment macro */ #define SA_SC_COPY(sa1, sa2, fn) \ do { \ (sa1)->scSysCall.sc_##fn.fptr.any = (sa2)->scSysCall.sc_##fn.fptr.any; \ (sa1)->scSysCall.sc_##fn.fctx = (sa2)->scSysCall.sc_##fn.fctx; \ } while (0) /* system call function call macros */ #define SA_SC_CALL_0(sa, fn) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx) \ : ((sa)->scSysCall.sc_##fn.fptr.std)() ) #define SA_SC_CALL_1(sa, fn, a1) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1) ) #define SA_SC_CALL_2(sa, fn, a1, a2) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2) ) #define SA_SC_CALL_3(sa, fn, a1, a2, a3) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3) ) #define SA_SC_CALL_4(sa, fn, a1, a2, a3, a4) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4) ) #define SA_SC_CALL_5(sa, fn, a1, a2, a3, a4, a5) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5) ) #define SA_SC_CALL_6(sa, fn, a1, a2, a3, a4, a5, a6) \ ( (sa)->scSysCall.sc_##fn.fctx != NULL \ ? ((sa)->scSysCall.sc_##fn.fptr.ctx)((sa)->scSysCall.sc_##fn.fctx, a1, a2, a3, a4, a5, a6) \ : ((sa)->scSysCall.sc_##fn.fptr.std)(a1, a2, a3, a4, a5, a6) ) /* system call table */ typedef struct { SA_SC_DECLARE_3(int, connect, int, const struct sockaddr *, socklen_t) SA_SC_DECLARE_3(int, accept, int, struct sockaddr *, socklen_t *) SA_SC_DECLARE_5(int, select, int, fd_set *, fd_set *, fd_set *, struct timeval *) SA_SC_DECLARE_3(ssize_t, read, int, void *, size_t) SA_SC_DECLARE_3(ssize_t, write, int, const void *, size_t) SA_SC_DECLARE_6(ssize_t, recvfrom, int, void *, size_t, int, struct sockaddr *, socklen_t *) SA_SC_DECLARE_6(ssize_t, sendto, int, const void *, size_t, int, const struct sockaddr *, socklen_t) } sa_syscall_tab_t; /* socket option information */ typedef struct { int todo; int value; } sa_optinfo_t; /* socket abstraction object */ struct sa_st { sa_type_t eType; /* socket type (stream or datagram) */ int fdSocket; /* socket file descriptor */ struct timeval tvTimeout[4]; /* timeout values (sec, usec) */ int nReadLen; /* read buffer current length */ int nReadSize; /* read buffer current size */ char *cpReadBuf; /* read buffer memory chunk */ int nWriteLen; /* write buffer current length */ int nWriteSize; /* write buffer current size */ char *cpWriteBuf; /* write buffer memory chunk */ sa_syscall_tab_t scSysCall; /* table of system calls */ sa_optinfo_t optInfo[5]; /* option storage */ }; /* socket address abstraction object */ struct sa_addr_st { int nFamily; /* the socket family (AF_XXX) */ struct sockaddr *saBuf; /* the "struct sockaddr_xx" actually */ socklen_t slBuf; /* the length of "struct sockaddr_xx" */ }; /* handy struct timeval check */ #define SA_TVISZERO(tv) \ ((tv).tv_sec == 0 && (tv).tv_usec == 0) /* convert Internet address from presentation to network format */ static int sa_inet_pton(int family, const char *strptr, void *addrptr) { #ifdef HAVE_INET_PTON return inet_pton(family, strptr, addrptr); #else struct in_addr in_val; if (family == AF_INET) { #if defined(HAVE_INET_ATON) /* at least for IPv4 we can rely on the old inet_aton(3) and for IPv6 inet_pton(3) would exist anyway */ if (inet_aton(strptr, &in_val) == 0) return 0; #elif defined(HAVE_INET_ADDR) /* at least for IPv4 try to rely on the even older inet_addr(3) */ memset(&in_val, '\0', sizeof(in_val)); if ((in_val.s_addr = inet_addr(strptr)) == ((in_addr_t)-1)) return 0; #endif memcpy(addrptr, &in_val, sizeof(struct in_addr)); return 1; } errno = EAFNOSUPPORT; return 0; #endif } /* convert Internet address from network to presentation format */ static const char *sa_inet_ntop(int family, const void *src, char *dst, size_t size) { #ifdef HAVE_INET_NTOP return inet_ntop(family, src, dst, size); #else char *cp; int n; if (family == AF_INET) { #ifdef HAVE_INET_NTOA /* at least for IPv4 we can rely on the old inet_ntoa(3) and for IPv6 inet_ntop(3) would exist anyway */ if ((cp = inet_ntoa(*((struct in_addr *)src))) == NULL) return NULL; n = strlen(cp); if (n > size-1) n = size-1; memcpy(dst, cp, n); dst[n] = '\0'; return dst; #endif } errno = EAFNOSUPPORT; return NULL; #endif } /* minimal output-independent vprintf(3) variant which supports %{c,s,d,%} only */ static int sa_mvxprintf(int (*output)(void *ctx, const char *buffer, size_t bufsize), void *ctx, const char *format, va_list ap) { /* sufficient integer buffer: x log_10(2) + safety */ char ibuf[((sizeof(int)*8)/3)+10]; char *cp; char c; int d; int n; int bytes; if (format == NULL) return -1; bytes = 0; while (*format != '\0') { if (*format == '%') { c = *(format+1); if (c == '%') { /* expand "%%" */ cp = &c; n = sizeof(char); } else if (c == 'c') { /* expand "%c" */ c = (char)va_arg(ap, int); cp = &c; n = sizeof(char); } else if (c == 's') { /* expand "%s" */ if ((cp = (char *)va_arg(ap, char *)) == NULL) cp = "(null)"; n = strlen(cp); } else if (c == 'd') { /* expand "%d" */ d = (int)va_arg(ap, int); #ifdef HAVE_SNPRINTF snprintf(ibuf, sizeof(ibuf), "%d", d); /* explicitly secure */ #else sprintf(ibuf, "%d", d); /* implicitly secure */ #endif cp = ibuf; n = strlen(cp); } else { /* any other "%X" */ cp = (char *)format; n = 2; } format += 2; } else { /* plain text */ cp = (char *)format; if ((format = strchr(cp, '%')) == NULL) format = strchr(cp, '\0'); n = format - cp; } /* perform output operation */ if (output != NULL) if ((n = output(ctx, cp, n)) == -1) break; bytes += n; } return bytes; } /* output callback function context for sa_mvsnprintf() */ typedef struct { char *bufptr; size_t buflen; } sa_mvsnprintf_cb_t; /* output callback function for sa_mvsnprintf() */ static int sa_mvsnprintf_cb(void *_ctx, const char *buffer, size_t bufsize) { sa_mvsnprintf_cb_t *ctx = (sa_mvsnprintf_cb_t *)_ctx; if (bufsize > ctx->buflen) return -1; memcpy(ctx->bufptr, buffer, bufsize); ctx->bufptr += bufsize; ctx->buflen -= bufsize; return bufsize; } /* minimal vsnprintf(3) variant which supports %{c,s,d} only */ static int sa_mvsnprintf(char *buffer, size_t bufsize, const char *format, va_list ap) { int n; sa_mvsnprintf_cb_t ctx; if (format == NULL) return -1; if (buffer != NULL && bufsize == 0) return -1; if (buffer == NULL) /* just determine output length */ n = sa_mvxprintf(NULL, NULL, format, ap); else { /* perform real output */ ctx.bufptr = buffer; ctx.buflen = bufsize; n = sa_mvxprintf(sa_mvsnprintf_cb, &ctx, format, ap); if (n != -1 && ctx.buflen == 0) n = -1; if (n != -1) *(ctx.bufptr) = '\0'; } return n; } /* minimal snprintf(3) variant which supports %{c,s,d} only */ static int sa_msnprintf(char *buffer, size_t bufsize, const char *format, ...) { int chars; va_list ap; /* pass through to va_list based variant */ va_start(ap, format); chars = sa_mvsnprintf(buffer, bufsize, format, ap); va_end(ap); return chars; } /* create address object */ sa_rc_t sa_addr_create(sa_addr_t **saap) { sa_addr_t *saa; /* argument sanity check(s) */ if (saap == NULL) return SA_RC(SA_ERR_ARG); /* allocate and initialize new address object */ if ((saa = (sa_addr_t *)malloc(sizeof(sa_addr_t))) == NULL) return SA_RC(SA_ERR_MEM); saa->nFamily = 0; saa->saBuf = NULL; saa->slBuf = 0; /* pass object to caller */ *saap = saa; return SA_OK; } /* destroy address object */ sa_rc_t sa_addr_destroy(sa_addr_t *saa) { /* argument sanity check(s) */ if (saa == NULL) return SA_RC(SA_ERR_ARG); /* free address objects and sub-object(s) */ if (saa->saBuf != NULL) free(saa->saBuf); free(saa); return SA_OK; } /* import URI into address object */ sa_rc_t sa_addr_u2a(sa_addr_t *saa, const char *uri, ...) { va_list ap; int sf; socklen_t sl; struct sockaddr *sa; struct sockaddr_un un; struct sockaddr_in sa4; #ifdef AF_INET6 struct sockaddr_in6 sa6; #endif int bIPv6; struct hostent *he; struct servent *se; int bNumeric; char *cpHost; char *cpPort; char *cpProto; unsigned int nPort; const char *cpPath; char uribuf[1024]; char *cp; int i; int n; /* argument sanity check(s) */ if (saa == NULL || uri == NULL) return SA_RC(SA_ERR_ARG); /* on-the-fly create or just take over URI */ va_start(ap, uri); sa_mvsnprintf(uribuf, sizeof(uribuf), uri, ap); va_end(ap); /* initialize result variables */ sa = NULL; sl = 0; sf = 0; /* parse URI and resolve contents. The following syntax is recognized: - unix: - inet://:[#(tcp|udp)] */ uri = uribuf; if (strncmp(uri, "unix:", 5) == 0) { /* parse URI */ cpPath = uri+5; /* mandatory(!) socket address structure initialization */ memset(&un, 0, sizeof(un)); /* fill-in socket address structure */ n = strlen(cpPath); if (n == 0) return SA_RC(SA_ERR_ARG); if ((n+1) > sizeof(un.sun_path)) return SA_RC(SA_ERR_MEM); if (cpPath[0] != '/') { if (getcwd(un.sun_path, sizeof(un.sun_path)-(n+1)) == NULL) return SA_RC(SA_ERR_MEM); cp = un.sun_path + strlen(un.sun_path); } else cp = un.sun_path; memcpy(cp, cpPath, n+1); un.sun_family = AF_LOCAL; /* provide results */ sa = (struct sockaddr *)&un; sl = sizeof(un); sf = AF_LOCAL; } else if (strncmp(uri, "inet://", 7) == 0) { /* parse URI into host, port and protocol parts */ cpHost = (char *)(uri+7); bIPv6 = FALSE; if (cpHost[0] == '[') { /* IPv6 address (see RFC2732) */ #ifndef AF_INET6 return SA_RC(SA_ERR_IMP); #else bIPv6 = TRUE; cpHost++; if ((cp = strchr(cpHost, ']')) == NULL) return SA_RC(SA_ERR_ARG); *cp++ = '\0'; if (*cp != ':') return SA_RC(SA_ERR_ARG); cp++; #endif } else { /* IPv4 address or hostname */ if ((cp = strrchr(cpHost, ':')) == NULL) return SA_RC(SA_ERR_ARG); *cp++ = '\0'; } cpPort = cp; cpProto = "tcp"; if ((cp = strchr(cpPort, '#')) != NULL) { *cp++ = '\0'; cpProto = cp; } /* resolve port */ nPort = 0; bNumeric = 1; for (i = 0; cpPort[i] != '\0'; i++) { if (!isdigit((int)cpPort[i])) { bNumeric = 0; break; } } if (bNumeric) nPort = (unsigned int)atoi(cpPort); else { if ((se = getservbyname(cpPort, cpProto)) == NULL) return SA_RC(SA_ERR_SYS); nPort = ntohs(se->s_port); } /* mandatory(!) socket address structure initialization */ memset(&sa4, 0, sizeof(sa4)); #ifdef AF_INET6 memset(&sa6, 0, sizeof(sa6)); #endif /* resolve host by trying to parse it as either directly an IPv4 or IPv6 address or by resolving it to either an IPv4 or IPv6 address */ if (!bIPv6 && sa_inet_pton(AF_INET, cpHost, &sa4.sin_addr.s_addr) == 1) { sa4.sin_family = AF_INET; sa4.sin_port = htons(nPort); sa = (struct sockaddr *)&sa4; sl = sizeof(sa4); sf = AF_INET; } #ifdef AF_INET6 else if (bIPv6 && sa_inet_pton(AF_INET6, cpHost, &sa6.sin6_addr.s6_addr) == 1) { sa6.sin6_family = AF_INET6; sa6.sin6_port = htons(nPort); sa = (struct sockaddr *)&sa6; sl = sizeof(sa6); sf = AF_INET6; } #endif else if ((he = gethostbyname(cpHost)) != NULL) { if (he->h_addrtype == AF_INET) { sa4.sin_family = AF_INET; sa4.sin_port = htons(nPort); memcpy(&sa4.sin_addr.s_addr, he->h_addr_list[0], sizeof(sa4.sin_addr.s_addr)); sa = (struct sockaddr *)&sa4; sl = sizeof(sa4); sf = AF_INET; } #ifdef AF_INET6 else if (he->h_addrtype == AF_INET6) { sa6.sin6_family = AF_INET6; sa6.sin6_port = htons(nPort); memcpy(&sa6.sin6_addr.s6_addr, he->h_addr_list[0], sizeof(sa6.sin6_addr.s6_addr)); sa = (struct sockaddr *)&sa6; sl = sizeof(sa6); sf = AF_INET6; } #endif else return SA_RC(SA_ERR_ARG); } else return SA_RC(SA_ERR_ARG); } else return SA_RC(SA_ERR_ARG); /* fill-in result address structure */ if (saa->saBuf != NULL) free(saa->saBuf); if ((saa->saBuf = (struct sockaddr *)malloc(sl)) == NULL) return SA_RC(SA_ERR_MEM); memcpy(saa->saBuf, sa, sl); saa->slBuf = sl; saa->nFamily = sf; return SA_OK; } /* import "struct sockaddr" into address object */ sa_rc_t sa_addr_s2a(sa_addr_t *saa, const struct sockaddr *sabuf, socklen_t salen) { /* argument sanity check(s) */ if (saa == NULL || sabuf == NULL || salen == 0) return SA_RC(SA_ERR_ARG); /* make sure we import only supported addresses */ if (!( sabuf->sa_family == AF_LOCAL || sabuf->sa_family == AF_INET #ifdef AF_INET6 || sabuf->sa_family == AF_INET6 #endif )) return SA_RC(SA_ERR_USE); /* create result address structure */ if (saa->saBuf != NULL) free(saa->saBuf); if ((saa->saBuf = (struct sockaddr *)malloc(salen)) == NULL) return SA_RC(SA_ERR_MEM); memcpy(saa->saBuf, sabuf, salen); saa->slBuf = salen; /* remember family */ saa->nFamily = sabuf->sa_family; return SA_OK; } /* export address object into URI */ sa_rc_t sa_addr_a2u(sa_addr_t *saa, char **uri) { char uribuf[1024]; struct sockaddr_un *un; struct sockaddr_in *sa4; #ifdef AF_INET6 struct sockaddr_in6 *sa6; #endif char caHost[512]; unsigned int nPort; /* argument sanity check(s) */ if (saa == NULL || uri == NULL) return SA_RC(SA_ERR_ARG); /* export object contents */ if (saa->nFamily == AF_LOCAL) { un = (struct sockaddr_un *)((void *)saa->saBuf); if ( ( saa->slBuf >= (socklen_t)(&(((struct sockaddr_un *)0)->sun_path[0])) && un->sun_path[0] == '\0') || saa->slBuf < sizeof(struct sockaddr_un)) /* in case the remote side of a Unix Domain socket was not bound, a "struct sockaddr_un" can occur with a length less than the expected one. Then there is actually no path at all. This has been verified under FreeBSD, Linux and Solaris. */ sa_msnprintf(uribuf, sizeof(uribuf), "unix:/NOT-BOUND"); else sa_msnprintf(uribuf, sizeof(uribuf), "unix:%s", un->sun_path); } else if (saa->nFamily == AF_INET) { sa4 = (struct sockaddr_in *)((void *)saa->saBuf); sa_inet_ntop(AF_INET, &sa4->sin_addr.s_addr, caHost, sizeof(caHost)); nPort = ntohs(sa4->sin_port); sa_msnprintf(uribuf, sizeof(uribuf), "inet://%s:%d", caHost, nPort); } #ifdef AF_INET6 else if (saa->nFamily == AF_INET6) { sa6 = (struct sockaddr_in6 *)((void *)saa->saBuf); sa_inet_ntop(AF_INET6, &sa6->sin6_addr.s6_addr, caHost, sizeof(caHost)); nPort = ntohs(sa6->sin6_port); sa_msnprintf(uribuf, sizeof(uribuf), "inet://[%s]:%d", caHost, nPort); } #endif else return SA_RC(SA_ERR_INT); /* pass result to caller */ *uri = strdup(uribuf); return SA_OK; } /* export address object into "struct sockaddr" */ sa_rc_t sa_addr_a2s(sa_addr_t *saa, struct sockaddr **sabuf, socklen_t *salen) { /* argument sanity check(s) */ if (saa == NULL || sabuf == NULL || salen == 0) return SA_RC(SA_ERR_ARG); /* export underlying address structure */ if ((*sabuf = (struct sockaddr *)malloc(saa->slBuf)) == NULL) return SA_RC(SA_ERR_MEM); memmove(*sabuf, saa->saBuf, saa->slBuf); *salen = saa->slBuf; return SA_OK; } sa_rc_t sa_addr_match(sa_addr_t *saa1, sa_addr_t *saa2, int prefixlen) { const unsigned char *ucp1, *ucp2; unsigned int uc1, uc2, mask; size_t l1, l2; int nBytes; int nBits; #ifdef AF_INET6 int i; const unsigned char *ucp0; #endif unsigned int np1, np2; int bMatchPort; /* argument sanity check(s) */ if (saa1 == NULL || saa2 == NULL) return SA_RC(SA_ERR_ARG); /* short circuiting for wildcard matching */ if (prefixlen == 0) return SA_OK; /* determine address representation pointer and size */ if (saa1->nFamily == AF_LOCAL) { np1 = 0; np2 = 0; ucp1 = (const unsigned char *)(((struct sockaddr_un *)saa1->saBuf)->sun_path); ucp2 = (const unsigned char *)(((struct sockaddr_un *)saa2->saBuf)->sun_path); l1 = strlen(((struct sockaddr_un *)saa1->saBuf)->sun_path) * 8; l2 = strlen(((struct sockaddr_un *)saa2->saBuf)->sun_path) * 8; if (prefixlen < 0) { if (l1 != l2) return SA_RC(SA_ERR_MTC); nBits = l1; } else { if (l1 < prefixlen || l2 < prefixlen) return SA_RC(SA_ERR_MTC); nBits = prefixlen; } } #ifdef AF_INET6 else if ( (saa1->nFamily == AF_INET && saa2->nFamily == AF_INET6) || (saa1->nFamily == AF_INET6 && saa2->nFamily == AF_INET )) { /* special case of comparing a regular IPv4 address (1.2.3.4) with an "IPv4-mapped IPv6 address" (::ffff:1.2.3.4). For details see RFC 2373. */ if (saa1->nFamily == AF_INET6) { np1 = (unsigned int)(((struct sockaddr_in6 *)saa1->saBuf)->sin6_port); np2 = (unsigned int)(((struct sockaddr_in *)saa2->saBuf)->sin_port); ucp1 = (const unsigned char *)&(((struct sockaddr_in6 *)saa1->saBuf)->sin6_addr); ucp2 = (const unsigned char *)&(((struct sockaddr_in *)saa2->saBuf)->sin_addr); ucp0 = ucp1; ucp1 += 12; } else { np1 = (unsigned int)(((struct sockaddr_in *)saa1->saBuf)->sin_port); np2 = (unsigned int)(((struct sockaddr_in6 *)saa2->saBuf)->sin6_port); ucp1 = (const unsigned char *)&(((struct sockaddr_in *)saa1->saBuf)->sin_addr); ucp2 = (const unsigned char *)&(((struct sockaddr_in6 *)saa2->saBuf)->sin6_addr); ucp0 = ucp2; ucp2 += 12; } for (i = 0; i < 10; i++) if (ucp0[i] != 0x00) return SA_RC(SA_ERR_MTC); if (!(ucp0[10] == 0xFF && ucp0[11] == 0xFF)) return SA_RC(SA_ERR_MTC); nBits = 32; } #endif else if (saa1->nFamily == AF_INET) { np1 = (unsigned int)(((struct sockaddr_in *)saa1->saBuf)->sin_port); np2 = (unsigned int)(((struct sockaddr_in *)saa2->saBuf)->sin_port); ucp1 = (const unsigned char *)&(((struct sockaddr_in *)saa1->saBuf)->sin_addr); ucp2 = (const unsigned char *)&(((struct sockaddr_in *)saa2->saBuf)->sin_addr); nBits = 32; } #ifdef AF_INET6 else if (saa1->nFamily == AF_INET6) { np1 = (unsigned int)(((struct sockaddr_in6 *)saa1->saBuf)->sin6_port); np2 = (unsigned int)(((struct sockaddr_in6 *)saa2->saBuf)->sin6_port); ucp1 = (const unsigned char *)&(((struct sockaddr_in6 *)saa1->saBuf)->sin6_addr); ucp2 = (const unsigned char *)&(((struct sockaddr_in6 *)saa2->saBuf)->sin6_addr); nBits = 128; } #endif else return SA_RC(SA_ERR_INT); /* make sure we do not compare than possible */ if (prefixlen > (nBits+1)) return SA_RC(SA_ERR_ARG); /* support equal matching (= all bits plus optionally port) */ bMatchPort = FALSE; if (prefixlen < 0) { if (prefixlen < -1) bMatchPort = TRUE; prefixlen = nBits; } /* perform address representation comparison (assumption guaranteed by API: network byte order is used) */ nBytes = (prefixlen / 8); nBits = (prefixlen % 8); if (nBytes > 0) { if (memcmp(ucp1, ucp2, nBytes) != 0) return SA_RC(SA_ERR_MTC); } if (nBits > 0) { uc1 = ucp1[nBytes]; uc2 = ucp2[nBytes]; mask = (0xFF << (8-nBits)) & 0xFF; if ((uc1 & mask) != (uc2 & mask)) return SA_RC(SA_ERR_MTC); } /* optionally perform additional port matching */ if (bMatchPort) if (np1 != np2) return SA_RC(SA_ERR_MTC); return SA_OK; } /* set timeouts if timeouts or done in kernel */ static sa_rc_t sa_socket_settimeouts(sa_t *sa) { /* stop processing if socket is still not allocated */ if (sa->fdSocket == -1) return SA_OK; #if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO) if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) { if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_RCVTIMEO, (const void *)(&sa->tvTimeout[SA_TIMEOUT_READ]), (socklen_t)(sizeof(sa->tvTimeout[SA_TIMEOUT_READ]))) < 0) return SA_RC(SA_ERR_SYS); } if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) { if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_SNDTIMEO, (const void *)(&sa->tvTimeout[SA_TIMEOUT_WRITE]), (socklen_t)(sizeof(sa->tvTimeout[SA_TIMEOUT_WRITE]))) < 0) return SA_RC(SA_ERR_SYS); } #endif return SA_OK; } /* set socket options */ static sa_rc_t sa_socket_setoptions(sa_t *sa) { int i; sa_rc_t rv; /* stop processing if socket is still not allocated */ if (sa->fdSocket == -1) return SA_OK; /* check for pending options */ rv = SA_OK; for (i = 0; i < (sizeof(sa->optInfo)/sizeof(sa->optInfo[0])); i++) { if (sa->optInfo[i].todo) { switch (i) { /* enable/disable Nagle's Algorithm (see RFC898) */ case SA_OPTION_NAGLE: { #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) int mode = sa->optInfo[i].value; if (setsockopt(sa->fdSocket, IPPROTO_TCP, TCP_NODELAY, (const void *)&mode, (socklen_t)sizeof(mode)) < 0) rv = SA_ERR_SYS; else sa->optInfo[i].todo = FALSE; #endif break; } /* enable/disable linger close semantics */ case SA_OPTION_LINGER: { #if defined(SO_LINGER) struct linger linger; linger.l_onoff = (sa->optInfo[i].value == 0 ? 0 : 1); linger.l_linger = sa->optInfo[i].value; if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_LINGER, (const void *)&linger, (socklen_t)sizeof(struct linger)) < 0) rv = SA_ERR_SYS; else sa->optInfo[i].todo = FALSE; #endif break; } /* enable/disable reusability of binding to address */ case SA_OPTION_REUSEADDR: { #if defined(SO_REUSEADDR) int mode = sa->optInfo[i].value; if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_REUSEADDR, (const void *)&mode, (socklen_t)sizeof(mode)) < 0) rv = SA_ERR_SYS; else sa->optInfo[i].todo = FALSE; #endif break; } /* enable/disable reusability of binding to port */ case SA_OPTION_REUSEPORT: { #if defined(SO_REUSEPORT) int mode = sa->optInfo[i].value; if (setsockopt(sa->fdSocket, SOL_SOCKET, SO_REUSEPORT, (const void *)&mode, (socklen_t)sizeof(mode)) < 0) rv = SA_ERR_SYS; else sa->optInfo[i].todo = FALSE; #endif break; } /* enable/disable non-blocking I/O mode */ case SA_OPTION_NONBLOCK: { int mode = sa->optInfo[i].value; int flags; if ((flags = fcntl(sa->fdSocket, F_GETFL, 0)) < 0) { rv = SA_ERR_SYS; break; } if (mode == 0) flags &= ~(O_NONBLOCK); else flags |= O_NONBLOCK; if (fcntl(sa->fdSocket, F_SETFL, flags) < 0) rv = SA_ERR_SYS; else sa->optInfo[i].todo = FALSE; break; } default: /* NOTREACHED */ break; } } } return SA_RC(rv); } /* internal lazy/delayed initialization of underlying socket */ static sa_rc_t sa_socket_init(sa_t *sa, int nFamily) { int nType; int nProto; #if !(defined(IPPROTO_TCP) && defined(IPPROTO_UDP)) struct protoent *pe; #endif sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* only perform operation if socket still does not exist */ if (sa->fdSocket != -1) return SA_RC(SA_ERR_USE); /* determine socket type */ if (sa->eType == SA_TYPE_STREAM) nType = SOCK_STREAM; else if (sa->eType == SA_TYPE_DATAGRAM) nType = SOCK_DGRAM; else return SA_RC(SA_ERR_INT); /* determine socket protocol */ if (nFamily == AF_LOCAL) nProto = 0; #ifdef AF_INET6 else if (nFamily == AF_INET || nFamily == AF_INET6) { #else else if (nFamily == AF_INET) { #endif #if defined(IPPROTO_TCP) && defined(IPPROTO_UDP) if (nType == SOCK_STREAM) nProto = IPPROTO_TCP; else if (nType == SOCK_DGRAM) nProto = IPPROTO_UDP; else return SA_RC(SA_ERR_INT); #else if (nType == SOCK_STREAM) pe = getprotobyname("tcp"); else if (nType == SOCK_DGRAM) pe = getprotobyname("udp"); else return SA_RC(SA_ERR_INT); if (pe == NULL) return SA_RC(SA_ERR_SYS); nProto = pe->p_proto; #endif } else return SA_RC(SA_ERR_INT); /* create the underlying socket */ if ((sa->fdSocket = socket(nFamily, nType, nProto)) == -1) return SA_RC(SA_ERR_SYS); /* optionally set kernel timeouts */ if ((rv = sa_socket_settimeouts(sa)) != SA_OK) return SA_RC(rv); /* optionally configure changed options */ if ((rv = sa_socket_setoptions(sa)) != SA_OK) return SA_RC(rv); return SA_OK; } /* internal destruction of underlying socket */ static sa_rc_t sa_socket_kill(sa_t *sa) { /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* check context */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* close socket */ close(sa->fdSocket); sa->fdSocket = -1; return SA_OK; } /* create abstract socket object */ sa_rc_t sa_create(sa_t **sap) { sa_t *sa; int i; /* argument sanity check(s) */ if (sap == NULL) return SA_RC(SA_ERR_ARG); /* allocate and initialize socket object */ if ((sa = (sa_t *)malloc(sizeof(sa_t))) == NULL) return SA_RC(SA_ERR_MEM); /* init object attributes */ sa->eType = SA_TYPE_STREAM; sa->fdSocket = -1; sa->nReadLen = 0; sa->nReadSize = 0; sa->cpReadBuf = NULL; sa->nWriteLen = 0; sa->nWriteSize = 0; sa->cpWriteBuf = NULL; /* init timeval object attributes */ for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) { sa->tvTimeout[i].tv_sec = 0; sa->tvTimeout[i].tv_usec = 0; } /* init options object attributes */ for (i = 0; i < (sizeof(sa->optInfo)/sizeof(sa->optInfo[0])); i++) { sa->optInfo[i].todo = FALSE; sa->optInfo[i].value = 0; } /* init syscall object attributes */ SA_SC_ASSIGN(sa, connect, connect, NULL); SA_SC_ASSIGN(sa, accept, accept, NULL); SA_SC_ASSIGN(sa, select, select, NULL); SA_SC_ASSIGN(sa, read, read, NULL); SA_SC_ASSIGN(sa, write, write, NULL); SA_SC_ASSIGN(sa, recvfrom, recvfrom, NULL); SA_SC_ASSIGN(sa, sendto, sendto, NULL); /* pass object to caller */ *sap = sa; return SA_OK; } /* destroy abstract socket object */ sa_rc_t sa_destroy(sa_t *sa) { /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* kill underlying socket */ sa_socket_kill(sa); /* free object and sub-objects */ if (sa->cpReadBuf != NULL) free(sa->cpReadBuf); if (sa->cpWriteBuf != NULL) free(sa->cpWriteBuf); free(sa); return SA_OK; } /* switch communication type of socket */ sa_rc_t sa_type(sa_t *sa, sa_type_t type) { /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); if (!(type == SA_TYPE_STREAM || type == SA_TYPE_DATAGRAM)) return SA_RC(SA_ERR_ARG); /* kill underlying socket if type changes */ if (sa->eType != type) sa_socket_kill(sa); /* set new type */ sa->eType = type; return SA_OK; } /* configure I/O timeout */ sa_rc_t sa_timeout(sa_t *sa, sa_timeout_t id, long sec, long usec) { int i; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); if (id == SA_TIMEOUT_ALL) { for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) { sa->tvTimeout[i].tv_sec = sec; sa->tvTimeout[i].tv_usec = usec; } } else { sa->tvTimeout[id].tv_sec = sec; sa->tvTimeout[id].tv_usec = usec; } /* try to already set timeouts */ if ((rv = sa_socket_settimeouts(sa)) != SA_OK) return SA_RC(rv); return SA_OK; } /* configure I/O buffers */ sa_rc_t sa_buffer(sa_t *sa, sa_buffer_t id, size_t size) { char *cp; /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); if (id == SA_BUFFER_READ) { /* configure read/incoming buffer */ if (sa->nReadLen > size) return SA_RC(SA_ERR_USE); if (size > 0) { if (sa->cpReadBuf == NULL) cp = (char *)malloc(size); else cp = (char *)realloc(sa->cpReadBuf, size); if (cp == NULL) return SA_RC(SA_ERR_MEM); sa->cpReadBuf = cp; sa->nReadSize = size; } else { if (sa->cpReadBuf != NULL) free(sa->cpReadBuf); sa->cpReadBuf = NULL; sa->nReadSize = 0; } } else if (id == SA_BUFFER_WRITE) { /* configure write/outgoing buffer */ if (sa->nWriteLen > size) return SA_RC(SA_ERR_USE); if (size > 0) { if (sa->cpWriteBuf == NULL) cp = (char *)malloc(size); else cp = (char *)realloc(sa->cpWriteBuf, size); if (cp == NULL) return SA_RC(SA_ERR_MEM); sa->cpWriteBuf = cp; sa->nWriteSize = size; } else { if (sa->cpWriteBuf != NULL) free(sa->cpWriteBuf); sa->cpWriteBuf = NULL; sa->nWriteSize = 0; } } else return SA_RC(SA_ERR_ARG); return SA_OK; } /* configure socket option */ sa_rc_t sa_option(sa_t *sa, sa_option_t id, ...) { sa_rc_t rv; va_list ap; /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* process option */ rv = SA_OK; va_start(ap, id); switch (id) { case SA_OPTION_NAGLE: { #if defined(IPPROTO_TCP) && defined(TCP_NODELAY) int mode = ((int)va_arg(ap, int) ? 1 : 0); sa->optInfo[SA_OPTION_NAGLE].value = mode; sa->optInfo[SA_OPTION_NAGLE].todo = TRUE; #else rv = SA_ERR_IMP; #endif break; } case SA_OPTION_LINGER: { #if defined(SO_LINGER) int amount = ((int)va_arg(ap, int) ? 1 : 0); sa->optInfo[SA_OPTION_LINGER].value = amount; sa->optInfo[SA_OPTION_LINGER].todo = TRUE; #else rv = SA_ERR_IMP; #endif break; } case SA_OPTION_REUSEADDR: { #if defined(SO_REUSEADDR) int mode = ((int)va_arg(ap, int) ? 1 : 0); sa->optInfo[SA_OPTION_REUSEADDR].value = mode; sa->optInfo[SA_OPTION_REUSEADDR].todo = TRUE; #else rv = SA_ERR_IMP; #endif break; } case SA_OPTION_REUSEPORT: { #if defined(SO_REUSEPORT) int mode = ((int)va_arg(ap, int) ? 1 : 0); sa->optInfo[SA_OPTION_REUSEPORT].value = mode; sa->optInfo[SA_OPTION_REUSEPORT].todo = TRUE; #else rv = SA_ERR_IMP; #endif break; } case SA_OPTION_NONBLOCK: { int mode = (int)va_arg(ap, int); sa->optInfo[SA_OPTION_NONBLOCK].value = mode; sa->optInfo[SA_OPTION_NONBLOCK].todo = TRUE; break; } default: { rv = SA_ERR_ARG; } } va_end(ap); /* if an error already occured, stop here */ if (rv != SA_OK) return SA_RC(rv); /* else already (re)set) options (if possible) */ if ((rv = sa_socket_setoptions(sa)) != SA_OK) return SA_RC(rv); return SA_OK; } /* override system call */ sa_rc_t sa_syscall(sa_t *sa, sa_syscall_t id, void (*fptr)(), void *fctx) { sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL || fptr == NULL) return SA_RC(SA_ERR_ARG); /* assign system call */ rv = SA_OK; switch (id) { case SA_SYSCALL_CONNECT: SA_SC_ASSIGN(sa, connect, fptr, fctx); break; case SA_SYSCALL_ACCEPT: SA_SC_ASSIGN(sa, accept, fptr, fctx); break; case SA_SYSCALL_SELECT: SA_SC_ASSIGN(sa, select, fptr, fctx); break; case SA_SYSCALL_READ: SA_SC_ASSIGN(sa, read, fptr, fctx); break; case SA_SYSCALL_WRITE: SA_SC_ASSIGN(sa, write, fptr, fctx); break; case SA_SYSCALL_RECVFROM: SA_SC_ASSIGN(sa, recvfrom, fptr, fctx); break; case SA_SYSCALL_SENDTO: SA_SC_ASSIGN(sa, sendto, fptr, fctx); break; default: rv = SA_ERR_ARG; } return SA_RC(rv); } /* bind socket to a local address */ sa_rc_t sa_bind(sa_t *sa, sa_addr_t *laddr) { sa_rc_t rv; struct sockaddr_un *un; /* argument sanity check(s) */ if (sa == NULL || laddr == NULL) return SA_RC(SA_ERR_ARG); /* lazy creation of underlying socket */ if (sa->fdSocket == -1) if ((rv = sa_socket_init(sa, laddr->nFamily)) != SA_OK) return SA_RC(rv); /* remove a possibly existing old Unix Domain socket on filesystem */ if (laddr->nFamily == AF_LOCAL) { un = (struct sockaddr_un *)((void *)laddr->saBuf); unlink(un->sun_path); } /* perform bind operation on underlying socket */ if (bind(sa->fdSocket, laddr->saBuf, laddr->slBuf) == -1) return SA_RC(SA_ERR_SYS); return SA_OK; } /* connect socket to a remote address */ sa_rc_t sa_connect(sa_t *sa, sa_addr_t *raddr) { int flags, n, error; fd_set rset, wset; socklen_t len; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL || raddr == NULL) return SA_RC(SA_ERR_ARG); /* connecting is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* lazy creation of underlying socket */ if (sa->fdSocket == -1) if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK) return SA_RC(rv); rv = SA_OK; if (SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_CONNECT])) { /* standard/non-timeout-aware connect operation */ if (SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf) < 0) rv = SA_ERR_SYS; } else { /* emulated/timeout-aware connect operation */ error = 0; /* temporarily switch underlying socket to non-blocking mode */ flags = fcntl(sa->fdSocket, F_GETFL, 0); fcntl(sa->fdSocket, F_SETFL, flags|O_NONBLOCK); /* perform the connect operation */ if ((n = SA_SC_CALL_3(sa, connect, sa->fdSocket, raddr->saBuf, raddr->slBuf)) < 0) { if (errno != EINPROGRESS) { error = errno; goto done; } } /* ok if connect completed immediately */ if (n == 0) goto done; /* wait for read or write possibility */ FD_ZERO(&rset); FD_ZERO(&wset); FD_SET(sa->fdSocket, &rset); FD_SET(sa->fdSocket, &wset); do { n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &rset, &wset, NULL, &sa->tvTimeout[SA_TIMEOUT_CONNECT]); } while (n == -1 && errno == EINTR); /* decide on return semantic */ if (n < 0) { error = errno; goto done; } else if (n == 0) { close(sa->fdSocket); /* stop TCP three-way handshake */ sa->fdSocket = -1; rv = SA_ERR_TMT; goto done; } /* fetch pending error */ len = sizeof(error); if (getsockopt(sa->fdSocket, SOL_SOCKET, SO_ERROR, (void *)&error, &len) < 0) error = errno; done: /* reset socket flags */ fcntl(sa->fdSocket, F_SETFL, flags); /* optionally set errno */ if (error != 0) { close(sa->fdSocket); /* just in case */ sa->fdSocket = -1; errno = error; rv = SA_ERR_SYS; } } return SA_RC(rv); } /* listen on socket for connections */ sa_rc_t sa_listen(sa_t *sa, int backlog) { /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* listening is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least sa_bind() has to be already performed */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* perform listen operation on underlying socket */ if (listen(sa->fdSocket, backlog) == -1) return SA_RC(SA_ERR_SYS); return SA_OK; } /* accept a connection on socket */ sa_rc_t sa_accept(sa_t *sa, sa_addr_t **caddr, sa_t **csa) { sa_rc_t rv; int n; fd_set fds; union { struct sockaddr_un un; struct sockaddr_in sa4; #ifdef AF_INET6 struct sockaddr_in6 sa6; #endif } sa_buf; socklen_t sa_size; int s; int i; /* argument sanity check(s) */ if (sa == NULL || caddr == NULL || csa == NULL) return SA_RC(SA_ERR_ARG); /* accepting connections is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least sa_listen() has to be already performed */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* if timeout is enabled, perform a smart-blocking wait */ if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_ACCEPT])) { FD_ZERO(&fds); FD_SET(sa->fdSocket, &fds); do { n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL, &sa->tvTimeout[SA_TIMEOUT_ACCEPT]); } while (n == -1 && errno == EINTR); if (n == 0) return SA_RC(SA_ERR_TMT); if (n <= 0) return SA_RC(SA_ERR_SYS); } /* perform accept operation on underlying socket */ sa_size = sizeof(sa_buf); if ((s = SA_SC_CALL_3(sa, accept, sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size)) == -1) return SA_RC(SA_ERR_SYS); /* create result address object */ if ((rv = sa_addr_create(caddr)) != SA_OK) return SA_RC(rv); if ((rv = sa_addr_s2a(*caddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) { sa_addr_destroy(*caddr); return SA_RC(rv); } /* create result socket object */ if ((rv = sa_create(csa)) != SA_OK) { sa_addr_destroy(*caddr); return SA_RC(rv); } /* fill-in child socket */ (*csa)->fdSocket = s; /* copy-over original system calls */ SA_SC_COPY((*csa), sa, connect); SA_SC_COPY((*csa), sa, accept); SA_SC_COPY((*csa), sa, select); SA_SC_COPY((*csa), sa, read); SA_SC_COPY((*csa), sa, write); SA_SC_COPY((*csa), sa, recvfrom); SA_SC_COPY((*csa), sa, sendto); /* copy-over original timeout values */ for (i = 0; i < (sizeof(sa->tvTimeout)/sizeof(sa->tvTimeout[0])); i++) { (*csa)->tvTimeout[i].tv_sec = sa->tvTimeout[i].tv_sec; (*csa)->tvTimeout[i].tv_usec = sa->tvTimeout[i].tv_usec; } return SA_OK; } /* determine remote address */ sa_rc_t sa_getremote(sa_t *sa, sa_addr_t **raddr) { sa_rc_t rv; union { struct sockaddr_in sa4; #ifdef AF_INET6 struct sockaddr_in6 sa6; #endif } sa_buf; socklen_t sa_size; /* argument sanity check(s) */ if (sa == NULL || raddr == NULL) return SA_RC(SA_ERR_ARG); /* peers exist only for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least sa_connect() or sa_accept() has to be already performed */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* determine remote address of underlying socket */ sa_size = sizeof(sa_buf); if (getpeername(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size) < 0) return SA_RC(SA_ERR_SYS); /* create result address object */ if ((rv = sa_addr_create(raddr)) != SA_OK) return SA_RC(rv); if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) { sa_addr_destroy(*raddr); return SA_RC(rv); } return SA_OK; } /* determine local address */ sa_rc_t sa_getlocal(sa_t *sa, sa_addr_t **laddr) { sa_rc_t rv; union { struct sockaddr_in sa4; #ifdef AF_INET6 struct sockaddr_in6 sa6; #endif } sa_buf; socklen_t sa_size; /* argument sanity check(s) */ if (sa == NULL || laddr == NULL) return SA_RC(SA_ERR_ARG); /* at least sa_bind() has to be already performed */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* determine local address of underlying socket */ sa_size = sizeof(sa_buf); if (getsockname(sa->fdSocket, (struct sockaddr *)&sa_buf, &sa_size) < 0) return SA_RC(SA_ERR_SYS); /* create result address object */ if ((rv = sa_addr_create(laddr)) != SA_OK) return SA_RC(rv); if ((rv = sa_addr_s2a(*laddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) { sa_addr_destroy(*laddr); return SA_RC(rv); } return SA_OK; } /* peek at underlying socket */ sa_rc_t sa_getfd(sa_t *sa, int *fd) { /* argument sanity check(s) */ if (sa == NULL || fd == NULL) return SA_RC(SA_ERR_ARG); /* if still no socket exists, say this explicitly */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* pass socket to caller */ *fd = sa->fdSocket; return SA_OK; } /* internal raw read operation */ static int sa_read_raw(sa_t *sa, char *cpBuf, int nBufLen) { int rv; #if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)) fd_set fds; #endif /* if timeout is enabled, perform explicit/smart blocking instead of implicitly/hard blocking in the read(2) system call */ #if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)) if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) { FD_ZERO(&fds); FD_SET(sa->fdSocket, &fds); do { rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL, &sa->tvTimeout[SA_TIMEOUT_READ]); } while (rv == -1 && errno == EINTR); if (rv == 0) { errno = ETIMEDOUT; return -1; } } #endif /* perform read operation on underlying socket */ do { rv = SA_SC_CALL_3(sa, read, sa->fdSocket, cpBuf, nBufLen); } while (rv == -1 && errno == EINTR); #if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO) if (rv == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT; #endif return rv; } /* read data from socket */ sa_rc_t sa_read(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes) { int n; sa_rc_t rv; int res; /* argument sanity check(s) */ if (sa == NULL || cpBuf == NULL || nBufReq == 0) return SA_RC(SA_ERR_ARG); /* reading is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* perform read operation */ rv = SA_OK; if (sa->nReadSize == 0) { /* user-space unbuffered I/O */ if (sa->nWriteLen > 0) sa_flush(sa); res = sa_read_raw(sa, cpBuf, nBufReq); if (res == 0) rv = SA_ERR_EOF; else if (res < 0 && errno == ETIMEDOUT) rv = SA_ERR_TMT; else if (res < 0) rv = SA_ERR_SYS; } else { /* user-space buffered I/O */ res = 0; for (;;) { if (nBufReq <= sa->nReadLen) { /* buffer holds enough data, so just use this */ memmove(cpBuf, sa->cpReadBuf, nBufReq); memmove(sa->cpReadBuf, sa->cpReadBuf+nBufReq, sa->nReadLen-nBufReq); sa->nReadLen -= nBufReq; res += nBufReq; } else { if (sa->nReadLen > 0) { /* fetch already existing buffer contents as a start */ memmove(cpBuf, sa->cpReadBuf, sa->nReadLen); nBufReq -= sa->nReadLen; cpBuf += sa->nReadLen; res += sa->nReadLen; sa->nReadLen = 0; } if (sa->nWriteLen > 0) sa_flush(sa); if (nBufReq >= sa->nReadSize) { /* buffer is too small at all, so read directly */ n = sa_read_raw(sa, cpBuf, nBufReq); if (n > 0) res += n; else if (n == 0) rv = (res == 0 ? SA_ERR_EOF : SA_OK); else if (n < 0 && errno == ETIMEDOUT) rv = (res == 0 ? SA_ERR_TMT : SA_OK); else if (n < 0) rv = (res == 0 ? SA_ERR_SYS : SA_OK); } else { /* fill buffer with new data */ n = sa_read_raw(sa, sa->cpReadBuf, sa->nReadSize); if (n < 0 && errno == ETIMEDOUT) /* timeout on this read, but perhaps ok as a whole */ rv = (res == 0 ? SA_ERR_TMT : SA_OK); else if (n < 0) /* error on this read, but perhaps ok as a whole */ rv = (res == 0 ? SA_ERR_SYS : SA_OK); else if (n == 0) /* EOF on this read, but perhaps ok as a whole */ rv = (res == 0 ? SA_ERR_EOF : SA_OK); else { sa->nReadLen = n; continue; /* REPEAT OPERATION! */ } } } break; } } /* pass number of actually read bytes to caller */ if (nBufRes != NULL) *nBufRes = (size_t)res; return SA_RC(rv); } /* read data from socket until [CR]LF (convinience function) */ sa_rc_t sa_readln(sa_t *sa, char *cpBuf, size_t nBufReq, size_t *nBufRes) { char c; size_t n; size_t res; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL || cpBuf == NULL || nBufReq == 0) return SA_RC(SA_ERR_ARG); /* reading is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* we just perform a plain sa_read() per character, because if buffers are enabled, this is not as stupid as it looks at the first hand and if buffers are disabled, there is no better solution anyway. */ rv = SA_OK; res = 0; while (res < (nBufReq-1)) { rv = sa_read(sa, &c, 1, &n); if (rv != SA_OK) break; if (n <= 0) break; cpBuf[res++] = c; if (c == '\n') break; } cpBuf[res] = '\0'; /* pass number of actually read characters to caller */ if (nBufRes != NULL) *nBufRes = res; return SA_RC(rv); } /* internal raw write operation */ static int sa_write_raw(sa_t *sa, const char *cpBuf, int nBufLen) { int rv; #if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)) fd_set fds; #endif /* if timeout is enabled, perform explicit/smart blocking instead of implicitly/hard blocking in the write(2) system call */ #if !(defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO)) if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) { FD_ZERO(&fds); FD_SET(sa->fdSocket, &fds); do { rv = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL, &sa->tvTimeout[SA_TIMEOUT_WRITE]); } while (rv == -1 && errno == EINTR); if (rv == 0) { errno = ETIMEDOUT; return -1; } } #endif /* perform write operation on underlying socket */ do { rv = SA_SC_CALL_3(sa, write, sa->fdSocket, cpBuf, nBufLen); } while (rv == -1 && errno == EINTR); #if defined(SO_RCVTIMEO) && defined(SO_SNDTIMEO) if (rv == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT; #endif return rv; } /* write data to socket */ sa_rc_t sa_write(sa_t *sa, const char *cpBuf, size_t nBufReq, size_t *nBufRes) { int n; int res; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL || cpBuf == NULL || nBufReq == 0) return SA_RC(SA_ERR_ARG); /* writing is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); rv = SA_OK; if (sa->nWriteSize == 0) { /* user-space unbuffered I/O */ res = sa_write_raw(sa, cpBuf, nBufReq); if (res < 0 && errno == ETIMEDOUT) rv = SA_ERR_TMT; else if (res < 0) rv = SA_ERR_SYS; } else { /* user-space buffered I/O */ if (nBufReq > (sa->nWriteSize - sa->nWriteLen)) { /* not enough space in buffer, so flush buffer first */ sa_flush(sa); } res = 0; if (nBufReq >= sa->nWriteSize) { /* buffer too small at all, so write immediately */ while (nBufReq > 0) { n = sa_write_raw(sa, cpBuf, nBufReq); if (n < 0 && errno == ETIMEDOUT) rv = (res == 0 ? SA_ERR_TMT : SA_OK); else if (n < 0) rv = (res == 0 ? SA_ERR_SYS : SA_OK); if (n <= 0) break; nBufReq -= n; cpBuf += n; res += n; } } else { /* (again) enough sprace in buffer, so store data */ memmove(sa->cpWriteBuf+sa->nWriteLen, cpBuf, nBufReq); sa->nWriteLen += nBufReq; res = nBufReq; } } /* pass number of actually written bytes to caller */ if (nBufRes != NULL) *nBufRes = (size_t)res; return SA_RC(rv); } /* output callback function context for sa_writef() */ typedef struct { sa_t *sa; sa_rc_t rv; } sa_writef_cb_t; /* output callback function for sa_writef() */ static int sa_writef_cb(void *_ctx, const char *buffer, size_t bufsize) { size_t n; sa_writef_cb_t *ctx = (sa_writef_cb_t *)_ctx; if ((ctx->rv = sa_write(ctx->sa, buffer, bufsize, &n)) != SA_OK) return -1; return (int)n; } /* write formatted string to socket (convinience function) */ sa_rc_t sa_writef(sa_t *sa, const char *cpFmt, ...) { va_list ap; size_t n; sa_writef_cb_t ctx; /* argument sanity check(s) */ if (sa == NULL || cpFmt == NULL) return SA_RC(SA_ERR_ARG); /* writing is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* format string into temporary buffer */ va_start(ap, cpFmt); ctx.sa = sa; ctx.rv = SA_OK; n = sa_mvxprintf(sa_writef_cb, &ctx, cpFmt, ap); va_end(ap); return ctx.rv; } /* flush write/outgoing I/O buffer */ sa_rc_t sa_flush(sa_t *sa) { int n; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL) return SA_RC(SA_ERR_ARG); /* flushing is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* try to flush buffer */ rv = SA_OK; if (sa->nWriteSize > 0) { while (sa->nWriteLen > 0) { n = sa_write_raw(sa, sa->cpWriteBuf, sa->nWriteLen); if (n < 0 && errno == ETIMEDOUT) rv = SA_ERR_TMT; else if (n < 0) rv = SA_ERR_SYS; if (n <= 0) break; memmove(sa->cpWriteBuf, sa->cpWriteBuf+n, sa->nWriteLen-n); sa->nWriteLen -= n; } sa->nWriteLen = 0; } return SA_RC(rv); } /* shutdown a socket connection */ sa_rc_t sa_shutdown(sa_t *sa, char *flags) { int how; /* argument sanity check(s) */ if (sa == NULL || flags == NULL) return SA_RC(SA_ERR_ARG); /* shutdown is only possible for stream communication */ if (sa->eType != SA_TYPE_STREAM) return SA_RC(SA_ERR_USE); /* at least a connection has to exist */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* determine flags for shutdown(2) */ how = 0; if (strcmp(flags, "r") == 0) how = SHUT_RD; else if (strcmp(flags, "w") == 0) how = SHUT_WR; else if (strcmp(flags, "rw") == 0 || strcmp(flags, "wr") == 0) how = SHUT_RDWR; else return SA_RC(SA_ERR_ARG); /* flush write buffers */ if ((how & SHUT_WR) || (how & SHUT_RDWR)) sa_flush(sa); /* perform shutdown operation on underlying socket */ if (shutdown(sa->fdSocket, how) == -1) return SA_RC(SA_ERR_SYS); return SA_OK; } /* receive data via socket */ sa_rc_t sa_recv(sa_t *sa, sa_addr_t **raddr, char *buf, size_t buflen, size_t *bufdone) { sa_rc_t rv; union { struct sockaddr_in sa4; #ifdef AF_INET6 struct sockaddr_in6 sa6; #endif } sa_buf; socklen_t sa_size; size_t n; fd_set fds; /* argument sanity check(s) */ if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL) return SA_RC(SA_ERR_ARG); /* receiving is only possible for datagram communication */ if (sa->eType != SA_TYPE_DATAGRAM) return SA_RC(SA_ERR_USE); /* at least a sa_bind() has to be performed */ if (sa->fdSocket == -1) return SA_RC(SA_ERR_USE); /* if timeout is enabled, perform explicit/smart blocking instead of implicitly/hard blocking in the recvfrom(2) system call */ if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_READ])) { FD_ZERO(&fds); FD_SET(sa->fdSocket, &fds); do { n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, &fds, NULL, NULL, &sa->tvTimeout[SA_TIMEOUT_READ]); } while (n == -1 && errno == EINTR); if (n == 0) errno = ETIMEDOUT; if (n <= 0) return SA_RC(SA_ERR_SYS); } /* perform receive operation on underlying socket */ sa_size = sizeof(sa_buf); if ((n = SA_SC_CALL_6(sa, recvfrom, sa->fdSocket, buf, buflen, 0, (struct sockaddr *)&sa_buf, &sa_size)) == -1) return SA_RC(SA_ERR_SYS); /* create result address object */ if ((rv = sa_addr_create(raddr)) != SA_OK) return rv; if ((rv = sa_addr_s2a(*raddr, (struct sockaddr *)&sa_buf, sa_size)) != SA_OK) { sa_addr_destroy(*raddr); return rv; } /* pass actual number of received bytes to caller */ if (bufdone != NULL) *bufdone = n; return SA_OK; } /* send data via socket */ sa_rc_t sa_send(sa_t *sa, sa_addr_t *raddr, const char *buf, size_t buflen, size_t *bufdone) { size_t n; fd_set fds; sa_rc_t rv; /* argument sanity check(s) */ if (sa == NULL || buf == NULL || buflen == 0 || raddr == NULL) return SA_RC(SA_ERR_ARG); /* sending is only possible for datagram communication */ if (sa->eType != SA_TYPE_DATAGRAM) return SA_RC(SA_ERR_USE); /* lazy creation of underlying socket */ if (sa->fdSocket == -1) if ((rv = sa_socket_init(sa, raddr->nFamily)) != SA_OK) return rv; /* if timeout is enabled, perform explicit/smart blocking instead of implicitly/hard blocking in the sendto(2) system call */ if (!SA_TVISZERO(sa->tvTimeout[SA_TIMEOUT_WRITE])) { FD_ZERO(&fds); FD_SET(sa->fdSocket, &fds); do { n = SA_SC_CALL_5(sa, select, sa->fdSocket+1, NULL, &fds, NULL, &sa->tvTimeout[SA_TIMEOUT_WRITE]); } while (n == -1 && errno == EINTR); if (n == 0) errno = ETIMEDOUT; if (n <= 0) return SA_RC(SA_ERR_SYS); } /* perform send operation on underlying socket */ if ((n = SA_SC_CALL_6(sa, sendto, sa->fdSocket, buf, buflen, 0, raddr->saBuf, raddr->slBuf)) == -1) return SA_RC(SA_ERR_SYS); /* pass actual number of sent bytes to caller */ if (bufdone != NULL) *bufdone = n; return SA_OK; } /* send formatted string to socket (convinience function) */ sa_rc_t sa_sendf(sa_t *sa, sa_addr_t *raddr, const char *cpFmt, ...) { va_list ap; size_t nBuf; char *cpBuf; sa_rc_t rv; char caBuf[1024]; /* argument sanity check(s) */ if (sa == NULL || raddr == NULL || cpFmt == NULL) return SA_RC(SA_ERR_ARG); /* format string into temporary buffer */ va_start(ap, cpFmt); nBuf = sa_mvsnprintf(NULL, 0, cpFmt, ap); va_end(ap); if ((nBuf+1) > sizeof(caBuf)) { /* requires a larger buffer, so allocate dynamically */ if ((cpBuf = (char *)malloc(nBuf+1)) == NULL) return SA_RC(SA_ERR_MEM); } else { /* fits into small buffer, so allocate statically */ cpBuf = caBuf; } va_start(ap, cpFmt); sa_mvsnprintf(cpBuf, nBuf+1, cpFmt, ap); va_end(ap); /* pass-through to sa_send() */ rv = sa_send(sa, raddr, cpBuf, nBuf, NULL); /* cleanup dynamically allocated buffer */ if ((nBuf+1) > sizeof(caBuf)) free(cpBuf); return rv; } /* return error string */ char *sa_error(sa_rc_t rv) { char *sz; /* translate result value into corresponding string */ if (rv == SA_OK) sz = "Everything Ok"; else if (rv == SA_ERR_ARG) sz = "Invalid Argument"; else if (rv == SA_ERR_USE) sz = "Invalid Use Or Context"; else if (rv == SA_ERR_MEM) sz = "Not Enough Memory"; else if (rv == SA_ERR_MTC) sz = "Matching Failed"; else if (rv == SA_ERR_EOF) sz = "End Of Communication"; else if (rv == SA_ERR_TMT) sz = "Communication Timeout"; else if (rv == SA_ERR_SYS) sz = "Operating System Error"; else if (rv == SA_ERR_IMP) sz = "Implementation Not Available"; else if (rv == SA_ERR_INT) sz = "Internal Error"; else sz = "Invalid Result Code"; return sz; }