diff options
Diffstat (limited to 'sockutil.c')
-rw-r--r-- | sockutil.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/sockutil.c b/sockutil.c new file mode 100644 index 000000000..4f41eee82 --- /dev/null +++ b/sockutil.c @@ -0,0 +1,549 @@ +/* Socket utility routines. + * + * (C) 2003 Anope Team + * Contact us at info@anope.org + * + * Please read COPYING and README for furhter details. + * + * Based on the original code of Epona by Lara. + * Based on the original code of Services by Andy Church. + * + * $Id: sockutil.c,v 1.7 2003/07/20 01:15:50 dane Exp $ + * + */ + +#include "services.h" + +/*************************************************************************/ +/*************************************************************************/ + +/* Read from a socket with buffering. */ + +static char read_netbuf[NET_BUFSIZE]; +static char *read_curpos = read_netbuf; /* Next byte to return */ +static char *read_bufend = read_netbuf; /* Next position for data from socket */ +static char *const read_buftop = read_netbuf + NET_BUFSIZE; +int32 total_read = 0; + + +/* Return amount of data in read buffer. */ + +int32 read_buffer_len() +{ + if (read_bufend >= read_curpos) + return read_bufend - read_curpos; + else + return (read_bufend + NET_BUFSIZE) - read_curpos; +} + + +/* Read data. */ + +static int buffered_read(int fd, char *buf, int len) +{ + int nread, left = len; + fd_set fds; + struct timeval tv = { 0, 0 }; + int errno_save = errno; + + if (fd < 0) { + errno = EBADF; + return -1; + } + while (left > 0) { + struct timeval *tvptr = (read_bufend == read_curpos ? NULL : &tv); + FD_ZERO(&fds); + FD_SET(fd, &fds); + while (read_bufend != read_curpos - 1 + && !(read_curpos == read_netbuf + && read_bufend == read_buftop - 1) + && select(fd + 1, &fds, 0, 0, tvptr) == 1) { + int maxread; + tvptr = &tv; /* don't wait next time */ + if (read_bufend < read_curpos) /* wrapped around? */ + maxread = (read_curpos - 1) - read_bufend; + else if (read_curpos == read_netbuf) + maxread = read_buftop - read_bufend - 1; + else + maxread = read_buftop - read_bufend; + nread = read(fd, read_bufend, maxread); + errno_save = errno; + if (debug >= 3) + alog("debug: buffered_read wanted %d, got %d", maxread, + nread); + if (nread <= 0) + break; + read_bufend += nread; + if (read_bufend == read_buftop) + read_bufend = read_netbuf; + } + if (read_curpos == read_bufend) /* No more data on socket */ + break; + /* See if we can gobble up the rest of the buffer. */ + if (read_curpos + left >= read_buftop && read_bufend < read_curpos) { + nread = read_buftop - read_curpos; + memcpy(buf, read_curpos, nread); + buf += nread; + left -= nread; + read_curpos = read_netbuf; + } + /* Now everything we need is in a single chunk at read_curpos. */ + if (read_bufend > read_curpos && read_bufend - read_curpos < left) + nread = read_bufend - read_curpos; + else + nread = left; + if (nread) { + memcpy(buf, read_curpos, nread); + buf += nread; + left -= nread; + read_curpos += nread; + } + } + total_read += len - left; + if (debug >= 4) { + alog("debug: buffered_read(%d,%p,%d) returning %d", + fd, buf, len, len - left); + } + errno = errno_save; + return len - left; +} + +/* Optimized version of the above for reading a single character; returns + * the character in an int or EOF, like fgetc(). */ + +static int buffered_read_one(int fd) +{ + int nread; + fd_set fds; + struct timeval tv = { 0, 0 }; + char c; + struct timeval *tvptr = (read_bufend == read_curpos ? NULL : &tv); + int errno_save = errno; + + if (fd < 0) { + errno = EBADF; + return -1; + } + FD_ZERO(&fds); + FD_SET(fd, &fds); + while (read_bufend != read_curpos - 1 + && !(read_curpos == read_netbuf + && read_bufend == read_buftop - 1) + && select(fd + 1, &fds, 0, 0, tvptr) == 1) { + int maxread; + tvptr = &tv; /* don't wait next time */ + if (read_bufend < read_curpos) /* wrapped around? */ + maxread = (read_curpos - 1) - read_bufend; + else if (read_curpos == read_netbuf) + maxread = read_buftop - read_bufend - 1; + else + maxread = read_buftop - read_bufend; + nread = read(fd, read_bufend, maxread); + errno_save = errno; + if (debug >= 3) + alog("debug: buffered_read_one wanted %d, got %d", maxread, + nread); + if (nread <= 0) + break; + read_bufend += nread; + if (read_bufend == read_buftop) + read_bufend = read_netbuf; + } + if (read_curpos == read_bufend) { /* No more data on socket */ + if (debug >= 4) + alog("debug: buffered_read_one(%d) returning %d", fd, EOF); + errno = errno_save; + return EOF; + } + c = *read_curpos++; + if (read_curpos == read_buftop) + read_curpos = read_netbuf; + total_read++; + if (debug >= 4) + alog("debug: buffered_read_one(%d) returning %d", fd, c); + return (int) c & 0xFF; +} + +/*************************************************************************/ + +/* Write to a socket with buffering. Note that this assumes only one + * socket. */ + +static char write_netbuf[NET_BUFSIZE]; +static char *write_curpos = write_netbuf; /* Next byte to write to socket */ +static char *write_bufend = write_netbuf; /* Next position for data to socket */ +static char *const write_buftop = write_netbuf + NET_BUFSIZE; +static int write_fd = -1; +int32 total_written; + + +/* Return amount of data in write buffer. */ + +int32 write_buffer_len() +{ + if (write_bufend >= write_curpos) + return write_bufend - write_curpos; + else + return (write_bufend + NET_BUFSIZE) - write_curpos; +} + + +/* Helper routine to try and write up to one chunk of data from the buffer + * to the socket. Return how much was written. */ + +static int flush_write_buffer(int wait) +{ + fd_set fds; + struct timeval tv = { 0, 0 }; + int errno_save = errno; + + if (write_bufend == write_curpos || write_fd == -1) + return 0; + FD_ZERO(&fds); + FD_SET(write_fd, &fds); + if (select(write_fd + 1, 0, &fds, 0, wait ? NULL : &tv) == 1) { + int maxwrite, nwritten; + if (write_curpos > write_bufend) /* wrapped around? */ + maxwrite = write_buftop - write_curpos; + else if (write_bufend == write_netbuf) + maxwrite = write_buftop - write_curpos - 1; + else + maxwrite = write_bufend - write_curpos; + nwritten = write(write_fd, write_curpos, maxwrite); + errno_save = errno; + if (debug >= 3) + alog("debug: flush_write_buffer wanted %d, got %d", maxwrite, + nwritten); + if (nwritten > 0) { + write_curpos += nwritten; + if (write_curpos == write_buftop) + write_curpos = write_netbuf; + total_written += nwritten; + return nwritten; + } + } + errno = errno_save; + return 0; +} + + +/* Write data. */ + +static int buffered_write(int fd, char *buf, int len) +{ + int nwritten, left = len; + int errno_save = errno; + + if (fd < 0) { + errno = EBADF; + return -1; + } + write_fd = fd; + + while (left > 0) { + + /* Don't try putting anything in the buffer if it's full. */ + if (write_curpos != write_bufend + 1 && + (write_curpos != write_netbuf + || write_bufend != write_buftop - 1)) { + /* See if we need to write up to the end of the buffer. */ + if (write_bufend + left >= write_buftop + && write_curpos <= write_bufend) { + nwritten = write_buftop - write_bufend; + memcpy(write_bufend, buf, nwritten); + buf += nwritten; + left -= nwritten; + write_bufend = write_netbuf; + } + /* Now we can copy a single chunk to write_bufend. */ + if (write_curpos > write_bufend + && write_curpos - write_bufend - 1 < left) + nwritten = write_curpos - write_bufend - 1; + else + nwritten = left; + if (nwritten) { + memcpy(write_bufend, buf, nwritten); + buf += nwritten; + left -= nwritten; + write_bufend += nwritten; + } + } + + /* Now write to the socket as much as we can. */ + if (write_curpos == write_bufend + 1 || + (write_curpos == write_netbuf + && write_bufend == write_buftop - 1)) + flush_write_buffer(1); + else + flush_write_buffer(0); + errno_save = errno; + if (write_curpos == write_bufend + 1 || + (write_curpos == write_netbuf + && write_bufend == write_buftop - 1)) { + /* Write failed on full buffer */ + break; + } + } + + if (debug >= 4) { + alog("debug: buffered_write(%d,%p,%d) returning %d", + fd, buf, len, len - left); + } + errno = errno_save; + return len - left; +} + +/* Optimized version of the above for writing a single character; returns + * the character in an int or EOF, like fputc(). Commented out because it + * isn't currently used. */ + +#if 0 +static int buffered_write_one(int c, int fd) +{ + struct timeval tv = { 0, 0 }; + + if (fd < 0) { + errno = EBADF; + return -1; + } + write_fd = fd; + + /* Try to flush the buffer if it's full. */ + if (write_curpos == write_bufend + 1 || + (write_curpos == write_netbuf + && write_bufend == write_buftop - 1)) { + flush_write_buffer(1); + if (write_curpos == write_bufend + 1 || + (write_curpos == write_netbuf + && write_bufend == write_buftop - 1)) { + /* Write failed */ + if (debug >= 4) + alog("debug: buffered_write_one(%d) returning %d", fd, + EOF); + return EOF; + } + } + + /* Write the character. */ + *write_bufend++ = c; + if (write_bufend == write_buftop) + write_bufend = write_netbuf; + + /* Move it to the socket if we can. */ + flush_write_buffer(0); + + if (debug >= 4) + alog("debug: buffered_write_one(%d) returning %d", fd, c); + return (int) c & 0xFF; +} +#endif /* 0 */ + +/*************************************************************************/ +/*************************************************************************/ + +static int lastchar = EOF; + +int sgetc(int s) +{ + int c; + + if (lastchar != EOF) { + c = lastchar; + lastchar = EOF; + return c; + } + return buffered_read_one(s); +} + +int sungetc(int c, int s) +{ + return lastchar = c; +} + +/*************************************************************************/ + +/* If connection was broken, return NULL. If the read timed out, return + * (char *)-1. + */ + +char *sgets(char *buf, int len, int s) +{ + int c = 0; + struct timeval tv; + fd_set fds; + char *ptr = buf; + + if (len == 0) + return NULL; + FD_SET(s, &fds); + tv.tv_sec = ReadTimeout; + tv.tv_usec = 0; + while (read_buffer_len() == 0 && + (c = select(s + 1, &fds, NULL, NULL, &tv)) < 0) { + if (errno != EINTR) + break; + } + if (read_buffer_len() == 0 && c == 0) + return (char *) -1; + c = sgetc(s); + while (--len && (*ptr++ = c) != '\n' && (c = sgetc(s)) >= 0); + if (c < 0) + return NULL; + *ptr = 0; + return buf; +} + +/*************************************************************************/ + +/* sgets2: Read a line of text from a socket, and strip newline and + * carriage return characters from the end of the line. + */ + +char *sgets2(char *buf, int len, int s) +{ + char *str = sgets(buf, len, s); + + if (!str || str == (char *) -1) + return str; + str = buf + strlen(buf) - 1; + if (*str == '\n') + *str-- = 0; + if (*str == '\r') + *str = 0; + return buf; +} + +/*************************************************************************/ + +/* Read from a socket. (Use this instead of read() because it has + * buffering.) */ + +int sread(int s, char *buf, int len) +{ + return buffered_read(s, buf, len); +} + +/*************************************************************************/ + +int sputs(char *str, int s) +{ + return buffered_write(s, str, strlen(str)); +} + +/*************************************************************************/ + +int sockprintf(int s, char *fmt, ...) +{ + va_list args; + char buf[16384]; /* Really huge, to try and avoid truncation */ + + va_start(args, fmt); + return buffered_write(s, buf, vsnprintf(buf, sizeof(buf), fmt, args)); +} + +/*************************************************************************/ +/*************************************************************************/ + +#if !HAVE_GETHOSTBYNAME + +/* Translate an IP dotted-quad address to a 4-byte character string. + * Return NULL if the given string is not in dotted-quad format. + */ + +static char *pack_ip(const char *ipaddr) +{ + static char ipbuf[4]; + int tmp[4], i; + + if (sscanf(ipaddr, "%d.%d.%d.%d", &tmp[0], &tmp[1], &tmp[2], &tmp[3]) + != 4) + return NULL; + for (i = 0; i < 4; i++) { + if (tmp[i] < 0 || tmp[i] > 255) + return NULL; + ipbuf[i] = tmp[i]; + } + return ipbuf; +} + +#endif + +/*************************************************************************/ + +/* lhost/lport specify the local side of the connection. If they are not + * given (lhost==NULL, lport==0), then they are left free to vary. + */ + +int conn(const char *host, int port, const char *lhost, int lport) +{ +#if HAVE_GETHOSTBYNAME + struct hostent *hp; +#else + char *addr; +#endif + struct sockaddr_in sa, lsa; + int sock; + + memset(&lsa, 0, sizeof(lsa)); + if (lhost) { +#if HAVE_GETHOSTBYNAME + if ((hp = gethostbyname(lhost)) != NULL) { + memcpy((char *) &lsa.sin_addr, hp->h_addr, hp->h_length); + lsa.sin_family = hp->h_addrtype; +#else + if (addr = pack_ip(lhost)) { + memcpy((char *) &lsa.sin_addr, addr, 4); + lsa.sin_family = AF_INET; +#endif + } else { + lhost = NULL; + } + } + if (lport) + lsa.sin_port = htons((unsigned short) lport); + + memset(&sa, 0, sizeof(sa)); +#if HAVE_GETHOSTBYNAME + if (!(hp = gethostbyname(host))) + return -1; + memcpy((char *) &sa.sin_addr, hp->h_addr, hp->h_length); + sa.sin_family = hp->h_addrtype; +#else + if (!(addr = pack_ip(host))) { + alog("conn(): `%s' is not a valid IP address", host); + errno = EINVAL; + return -1; + } + memcpy((char *) &sa.sin_addr, addr, 4); + sa.sin_family = AF_INET; +#endif + sa.sin_port = htons((unsigned short) port); + + if ((sock = socket(sa.sin_family, SOCK_STREAM, 0)) < 0) + return -1; + + if ((lhost || lport) + && bind(sock, (struct sockaddr *) &lsa, sizeof(lsa)) < 0) { + int errno_save = errno; + close(sock); + errno = errno_save; + return -1; + } + + if (connect(sock, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + int errno_save = errno; + close(sock); + errno = errno_save; + return -1; + } + + return sock; +} + +/*************************************************************************/ + +void disconn(int s) +{ + shutdown(s, 2); + close(s); +} |