diff --git a/apps/Makefile.am.tpl b/apps/Makefile.am.tpl index deae953..16b3d8b 100644 --- a/apps/Makefile.am.tpl +++ b/apps/Makefile.am.tpl @@ -14,3 +14,8 @@ if !HAVE_STRTONUM openssl_SOURCES += strtonum.c endif +if !HAVE_POLL +if HOST_WIN +openssl_SOURCES += poll.c +endif +endif diff --git a/apps/poll.c b/apps/poll.c new file mode 100644 index 0000000..bf30ccf --- /dev/null +++ b/apps/poll.c @@ -0,0 +1,334 @@ +/* + * Public domain + * + * poll(2) emulation for Windows + * + * This emulates just-enough poll functionality on Windows to work in the + * context of the openssl(1) program. This is not a replacement for + * POSIX.1-2001 poll(2), though it may come closer than I care to admit. + * + * Dongsheng Song + * Brent Cook + */ + +#include +#include +#include +#include +#include + +static int +conn_is_closed(int fd) +{ + char buf[1]; + int ret = recv(fd, buf, 1, MSG_PEEK); + if (ret == -1) { + switch (WSAGetLastError()) { + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAENETRESET: + case WSAESHUTDOWN: + return 1; + } + } + return 0; +} + +static int +conn_has_oob_data(int fd) +{ + char buf[1]; + return (recv(fd, buf, 1, MSG_PEEK | MSG_OOB) == 1); +} + +static int +is_socket(int fd) +{ + WSANETWORKEVENTS events; + return (WSAEnumNetworkEvents((SOCKET)fd, NULL, &events) == 0); +} + +static int +compute_select_revents(int fd, short events, + fd_set *rfds, fd_set *wfds, fd_set *efds) +{ + int rc = 0; + + if ((events & (POLLIN | POLLRDNORM | POLLRDBAND)) && + FD_ISSET(fd, rfds)) { + if (conn_is_closed(fd)) + rc |= POLLHUP; + else + rc |= POLLIN | POLLRDNORM; + } + + if ((events & (POLLOUT | POLLWRNORM | POLLWRBAND)) && + FD_ISSET(fd, wfds)) + rc |= POLLOUT; + + if (FD_ISSET(fd, efds)) { + if (conn_is_closed(fd)) + rc |= POLLHUP; + else if (conn_has_oob_data(fd)) + rc |= POLLRDBAND | POLLPRI; + } + + return rc; +} + +static int +compute_wait_revents(HANDLE h, short events, int object, int wait_rc) +{ + int rc = 0; + INPUT_RECORD record; + DWORD num_read; + + /* + * Assume we can always write to file handles (probably a bad + * assumption but works for now, at least it doesn't block). + */ + if (events & (POLLOUT | POLLWRNORM)) + rc |= POLLOUT; + + /* + * Check if this handle was signaled by WaitForMultipleObjects + */ + if (wait_rc >= WAIT_OBJECT_0 && (object == (wait_rc - WAIT_OBJECT_0)) + && (events & (POLLIN | POLLRDNORM))) { + + /* + * Check if this file is stdin, and if so, if it is a console. + */ + if (h == GetStdHandle(STD_INPUT_HANDLE) && + PeekConsoleInput(h, &record, 1, &num_read) == 1) { + + /* + * Handle the input console buffer differently, + * since it can signal on other events like + * window and mouse, but read can still block. + */ + if (record.EventType == KEY_EVENT && + record.Event.KeyEvent.bKeyDown) { + rc |= POLLIN; + } else { + /* + * Flush non-character events from the + * console buffer. + */ + ReadConsoleInput(h, &record, 1, &num_read); + } + } else { + rc |= POLLIN; + } + } + + return rc; +} + +static int +wsa_select_errno(int err) +{ + switch (err) { + case WSAEINTR: + case WSAEINPROGRESS: + errno = EINTR; + break; + case WSAEFAULT: + /* + * Windows uses WSAEFAULT for both resource allocation failures + * and arguments not being contained in the user's address + * space. So, we have to choose EFAULT or ENOMEM. + */ + errno = EFAULT; + break; + case WSAEINVAL: + errno = EINVAL; + break; + case WSANOTINITIALISED: + errno = EPERM; + break; + case WSAENETDOWN: + errno = ENOMEM; + break; + } + return -1; +} + +int +poll(struct pollfd *pfds, nfds_t nfds, int timeout_ms) +{ + nfds_t i; + int timespent_ms, looptime_ms; + +#define FD_IS_SOCKET (1 << 0) + int fd_state[FD_SETSIZE]; + int num_fds; + + /* + * select machinery + */ + fd_set rfds, wfds, efds; + int rc; + int num_sockets; + + /* + * wait machinery + */ + DWORD wait_rc; + HANDLE handles[FD_SETSIZE]; + int num_handles; + + if (pfds == NULL) { + errno = EINVAL; + return -1; + } + + if (nfds <= 0) { + return 0; + } + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + FD_ZERO(&efds); + num_fds = 0; + num_sockets = 0; + num_handles = 0; + + for (i = 0; i < nfds; i++) { + if ((int)pfds[i].fd < 0) { + continue; + } + + if (is_socket(pfds[i].fd)) { + if (num_sockets >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } + + fd_state[num_fds] = FD_IS_SOCKET; + + FD_SET(pfds[i].fd, &efds); + + if (pfds[i].events & + (POLLIN | POLLRDNORM | POLLRDBAND)) { + FD_SET(pfds[i].fd, &rfds); + } + + if (pfds[i].events & + (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET(pfds[i].fd, &wfds); + } + num_sockets++; + + } else { + if (num_handles >= FD_SETSIZE) { + errno = EINVAL; + return -1; + } + + handles[num_handles++] = + (HANDLE)_get_osfhandle(pfds[i].fd); + } + + num_fds++; + } + + /* + * Determine if the files, pipes, sockets, consoles, etc. have signaled. + * + * Do this by alternating a loop between WaitForMultipleObjects for + * non-sockets and and select for sockets. + * + * I tried to implement this all in terms of WaitForMultipleObjects + * with a select-based 'poll' of the sockets at the end to get extra + * specific socket status. + * + * However, the cost of setting up an event handle for each socket and + * cleaning them up reliably was pretty high. Since the event handle + * associated with a socket is also global, creating a new one here + * cancels one that may exist externally to this function. + * + * At any rate, even if global socket event handles were not an issue, + * the 'FD_WRITE' status of a socket event handle does not behave in an + * expected fashion, being triggered by an edge on a write buffer rather + * than simply triggering if there is space available. + */ + timespent_ms = 0; + wait_rc = 0; + + if (timeout_ms < 0) { + timeout_ms = INFINITE; + } + looptime_ms = timeout_ms > 100 ? 100 : timeout_ms; + + do { + struct timeval tv = {0, looptime_ms * 1000}; + + /* + * Check if any file handles have signaled + */ + if (num_handles) { + wait_rc = WaitForMultipleObjects(num_handles, handles, FALSE, 0); + if (wait_rc == WAIT_FAILED) { + /* + * The documentation for WaitForMultipleObjects + * does not specify what values GetLastError + * may return here. Rather than enumerate + * badness like for wsa_select_errno, assume a + * general errno value. + */ + errno = ENOMEM; + return 0; + } + } + + /* + * If we signaled on a file handle, don't wait on the sockets. + */ + if (wait_rc >= WAIT_OBJECT_0) + tv.tv_usec = 0; + + /* + * Check if any sockets have signaled + */ + rc = select(0, &rfds, &wfds, &efds, &tv); + if (rc == SOCKET_ERROR) { + return wsa_select_errno(WSAGetLastError()); + } + + if (wait_rc >= WAIT_OBJECT_0 || (num_sockets && rc > 0)) + break; + + timespent_ms += looptime_ms; + + } while (timespent_ms < timeout_ms); + + rc = 0; + num_handles = 0; + num_fds = 0; + for (i = 0; i < nfds; i++) { + pfds[i].revents = 0; + + if ((int)pfds[i].fd < 0) + continue; + + if (fd_state[num_fds] & FD_IS_SOCKET) { + pfds[i].revents = compute_select_revents(pfds[i].fd, + pfds[i].events, &rfds, &wfds, &efds); + + } else { + pfds[i].revents = compute_wait_revents( + handles[num_handles], pfds[i].events, num_handles, + wait_rc); + num_handles++; + } + + num_fds++; + + if (pfds[i].revents) + rc++; + } + + return rc; +} + diff --git a/configure.ac b/configure.ac index dfa59b5..b434190 100644 --- a/configure.ac +++ b/configure.ac @@ -77,7 +77,7 @@ CFLAGS="$CFLAGS $CLANG_CFLAGS" LDFLAGS="$LDFLAGS $CLANG_FLAGS" AC_CHECK_FUNCS([arc4random_buf asprintf explicit_bzero funopen getauxval]) -AC_CHECK_FUNCS([getentropy issetugid memmem reallocarray]) +AC_CHECK_FUNCS([getentropy issetugid memmem poll reallocarray]) AC_CHECK_FUNCS([strlcat strlcpy strndup strnlen strtonum]) AC_CHECK_FUNCS([timingsafe_bcmp timingsafe_memcmp]) @@ -88,6 +88,7 @@ AM_CONDITIONAL([HAVE_EXPLICIT_BZERO], [test "x$ac_cv_func_explicit_bzero" = xyes AM_CONDITIONAL([HAVE_GETENTROPY], [test "x$ac_cv_func_getentropy" = xyes]) AM_CONDITIONAL([HAVE_ISSETUGID], [test "x$ac_cv_func_issetugid" = xyes]) AM_CONDITIONAL([HAVE_MEMMEM], [test "x$ac_cv_func_memmem" = xyes]) +AM_CONDITIONAL([HAVE_POLL], [test "x$ac_cv_func_poll" = xyes]) AM_CONDITIONAL([HAVE_REALLOCARRAY], [test "x$ac_cv_func_reallocarray" = xyes]) AM_CONDITIONAL([HAVE_STRLCAT], [test "x$ac_cv_func_strlcat" = xyes]) AM_CONDITIONAL([HAVE_STRLCPY], [test "x$ac_cv_func_strlcpy" = xyes]) diff --git a/include/poll.h b/include/poll.h new file mode 100644 index 0000000..37f32cd --- /dev/null +++ b/include/poll.h @@ -0,0 +1,56 @@ +/* + * poll(2) emulation for Windows + * Public domain + * from Dongsheng Song + */ + +#ifndef LIBCRYPTOCOMPAT_POLL_H +#define LIBCRYPTOCOMPAT_POLL_H + +#ifdef HAVE_POLL +#include_next +#else + +#include + +/* Type used for the number of file descriptors. */ +typedef unsigned long int nfds_t; + +#if !defined(_WIN32_WINNT) || (_WIN32_WINNT < 0x0600) +/* Data structure describing a polling request. */ +struct pollfd { + int fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +}; + +/* Event types that can be polled */ +#define POLLIN 0x001 /* There is data to read. */ +#define POLLPRI 0x002 /* There is urgent data to read. */ +#define POLLOUT 0x004 /* Writing now will not block. */ + +# define POLLRDNORM 0x040 /* Normal data may be read. */ +# define POLLRDBAND 0x080 /* Priority data may be read. */ +# define POLLWRNORM 0x100 /* Writing now will not block. */ +# define POLLWRBAND 0x200 /* Priority data may be written. */ + +/* Event types always implicitly polled. */ +#define POLLERR 0x008 /* Error condition. */ +#define POLLHUP 0x010 /* Hung up. */ +#define POLLNVAL 0x020 /* Invalid polling request. */ + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int poll(struct pollfd *pfds, nfds_t nfds, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_POLL */ + +#endif /* LIBCRYPTOCOMPAT_POLL_H */ diff --git a/update.sh b/update.sh index cda2766..1038bb3 100755 --- a/update.sh +++ b/update.sh @@ -283,7 +283,6 @@ copy_crypto x509v3 "v3_bcons.c v3_bitst.c v3_conf.c v3_extku.c v3_ia5.c v3_lib.c pcy_cache.c pcy_node.c pcy_data.c pcy_map.c pcy_tree.c pcy_lib.c pcy_int.h ext_dat.h" -rm -f apps/*.c apps/*.h for i in $openssl_cmd_src/*; do cp $i apps done @@ -444,6 +443,7 @@ crypto_win32_only=( # conditional compiles $CP $libc_src/stdlib/strtonum.c apps/ apps_excludes=( + poll.c strtonum.c ) apps_posix_only=(