diff options
author | Corinna Vinschen <corinna@vinschen.de> | 2022-08-04 17:58:50 +0300 |
---|---|---|
committer | Corinna Vinschen <corinna@vinschen.de> | 2022-08-05 13:02:11 +0300 |
commit | 007e23d6390af11582e55453269b7a51c723d2dd (patch) | |
tree | 8e8cff3ca23f5e56d9766a5ee6c6abb366611b07 /winsup/cygwin/fhandler/socket_local.cc | |
parent | 1e428bee1c5ef7c76ba4e46e6693b913edc9bbf3 (diff) |
Cygwin: Reorganize cygwin source dir
Create subdirs and move files accordingly:
- DevDocs: doc files
- fhandler: fhandler sources, split fhandler.cc into base.cc and null.cc
- local_includes: local include files
- scripts: scripts called during build
- sec: security sources
Signed-off-by: Corinna Vinschen <corinna@vinschen.de>
Diffstat (limited to 'winsup/cygwin/fhandler/socket_local.cc')
-rw-r--r-- | winsup/cygwin/fhandler/socket_local.cc | 1691 |
1 files changed, 1691 insertions, 0 deletions
diff --git a/winsup/cygwin/fhandler/socket_local.cc b/winsup/cygwin/fhandler/socket_local.cc new file mode 100644 index 000000000..e4a88169b --- /dev/null +++ b/winsup/cygwin/fhandler/socket_local.cc @@ -0,0 +1,1691 @@ +/* fhandler_socket_local.cc. + + See fhandler.h for a description of the fhandler classes. + + This file is part of Cygwin. + + This software is a copyrighted work licensed under the terms of the + Cygwin license. Please consult the file "CYGWIN_LICENSE" for + details. */ + +#define __INSIDE_CYGWIN_NET__ +#define USE_SYS_TYPES_FD_SET + +#include "winsup.h" +/* 2014-04-24: Current Mingw headers define sockaddr_in6 using u_long (8 byte) + because a redefinition for LP64 systems is missing. This leads to a wrong + definition and size of sockaddr_in6 when building with winsock headers. + This definition is also required to use the right u_long type in subsequent + function calls. */ +#undef u_long +#define u_long __ms_u_long +#include "ntsecapi.h" +#include <w32api/ws2tcpip.h> +#include <w32api/mswsock.h> +#include <unistd.h> +#include <asm/byteorder.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/param.h> +#include <sys/statvfs.h> +#include <cygwin/acl.h> +#include "cygerrno.h" +#include "path.h" +#include "fhandler.h" +#include "dtable.h" +#include "cygheap.h" +#include "wininfo.h" +#include "ntdll.h" + +extern "C" { + int sscanf (const char *, const char *, ...); +} /* End of "C" section */ + +#define ASYNC_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT) +#define EVENT_MASK (FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE) + +#define LOCK_EVENTS \ + if (wsock_mtx && \ + WaitForSingleObject (wsock_mtx, INFINITE) != WAIT_FAILED) \ + { + +#define UNLOCK_EVENTS \ + ReleaseMutex (wsock_mtx); \ + } + +static inline mode_t +adjust_socket_file_mode (mode_t mode) +{ + /* Kludge: Don't allow to remove read bit on socket files for + user/group/other, if the accompanying write bit is set. It would + be nice to have exact permissions on a socket file, but it's + necessary that somebody able to access the socket can always read + the contents of the socket file to avoid spurious "permission + denied" messages. */ + return mode | ((mode & (S_IWUSR | S_IWGRP | S_IWOTH)) << 1); +} + +/* cygwin internal: map sockaddr into internet domain address */ +int +get_inet_addr_local (const struct sockaddr *in, int inlen, + struct sockaddr_storage *out, int *outlen, + int *type = NULL, int *secret = NULL) +{ + int secret_buf [4]; + int* secret_ptr = (secret ? : secret_buf); + + /* Check for abstract socket. These are generated for AF_LOCAL datagram + sockets in recv_internal, to allow a datagram server to use sendto + after recvfrom. */ + if (inlen >= (int) sizeof (in->sa_family) + 7 + && in->sa_data[0] == '\0' && in->sa_data[1] == 'd' + && in->sa_data[6] == '\0') + { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + sscanf (in->sa_data + 2, "%04hx", &addr.sin_port); + addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + *outlen = sizeof addr; + memcpy (out, &addr, *outlen); + return 0; + } + + path_conv pc (in->sa_data, PC_SYM_FOLLOW); + if (pc.error) + { + set_errno (pc.error); + return SOCKET_ERROR; + } + if (!pc.exists ()) + { + set_errno (ENOENT); + return SOCKET_ERROR; + } + /* Do NOT test for the file being a socket file here. The socket file + creation is not an atomic operation, so there is a chance that socket + files which are just in the process of being created are recognized + as non-socket files. To work around this problem we now create the + file with all sharing disabled. If the below NtOpenFile fails + with STATUS_SHARING_VIOLATION we know that the file already exists, + but the creating process isn't finished yet. So we yield and try + again, until we can either open the file successfully, or some error + other than STATUS_SHARING_VIOLATION occurs. + Since we now don't know if the file is actually a socket file, we + perform this check here explicitely. */ + NTSTATUS status; + HANDLE fh; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + + pc.get_object_attr (attr, sec_none_nih); + do + { + status = NtOpenFile (&fh, GENERIC_READ | SYNCHRONIZE, &attr, &io, + FILE_SHARE_VALID_FLAGS, + FILE_SYNCHRONOUS_IO_NONALERT + | FILE_OPEN_FOR_BACKUP_INTENT + | FILE_NON_DIRECTORY_FILE); + if (status == STATUS_SHARING_VIOLATION) + { + /* While we hope that the sharing violation is only temporary, we + also could easily get stuck here, waiting for a file in use by + some greedy Win32 application. Therefore we should never wait + endlessly without checking for signals and thread cancel event. */ + pthread_testcancel (); + if (cygwait (NULL, cw_nowait, cw_sig_eintr) == WAIT_SIGNALED + && !_my_tls.call_signal_handler ()) + { + set_errno (EINTR); + return SOCKET_ERROR; + } + yield (); + } + else if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return SOCKET_ERROR; + } + } + while (status == STATUS_SHARING_VIOLATION); + /* Now test for the SYSTEM bit. */ + FILE_BASIC_INFORMATION fbi; + status = NtQueryInformationFile (fh, &io, &fbi, sizeof fbi, + FileBasicInformation); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + return SOCKET_ERROR; + } + if (!(fbi.FileAttributes & FILE_ATTRIBUTE_SYSTEM)) + { + NtClose (fh); + set_errno (EBADF); + return SOCKET_ERROR; + } + /* Eventually check the content and fetch the required information. */ + char buf[128]; + memset (buf, 0, sizeof buf); + status = NtReadFile (fh, NULL, NULL, NULL, &io, buf, 128, NULL, NULL); + NtClose (fh); + if (NT_SUCCESS (status)) + { + struct sockaddr_in sin; + char ctype; + sin.sin_family = AF_INET; + if (strncmp (buf, SOCKET_COOKIE, strlen (SOCKET_COOKIE))) + { + set_errno (EBADF); + return SOCKET_ERROR; + } + sscanf (buf + strlen (SOCKET_COOKIE), "%hu %c %08x-%08x-%08x-%08x", + &sin.sin_port, + &ctype, + secret_ptr, secret_ptr + 1, secret_ptr + 2, secret_ptr + 3); + sin.sin_port = htons (sin.sin_port); + sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + memcpy (out, &sin, sizeof sin); + *outlen = sizeof sin; + if (type) + *type = (ctype == 's' ? SOCK_STREAM : + ctype == 'd' ? SOCK_DGRAM + : 0); + return 0; + } + __seterrno_from_nt_status (status); + return SOCKET_ERROR; +} + +/* There's no DLL which exports the symbol WSARecvMsg. One has to call + WSAIoctl as below to fetch the function pointer. Why on earth did the + MS developers decide not to export a normal symbol for these extension + functions? */ +inline int +get_ext_funcptr (SOCKET sock, void *funcptr) +{ + DWORD bret; + const GUID guid = WSAID_WSARECVMSG; + return WSAIoctl (sock, SIO_GET_EXTENSION_FUNCTION_POINTER, + (void *) &guid, sizeof (GUID), funcptr, sizeof (void *), + &bret, NULL, NULL); +} + +fhandler_socket_local::fhandler_socket_local () : + fhandler_socket_wsock (), + sun_path (NULL), + peer_sun_path (NULL), + status () +{ +} + +fhandler_socket_local::~fhandler_socket_local () +{ + if (sun_path) + cfree (sun_path); + if (peer_sun_path) + cfree (peer_sun_path); +} + +int +fhandler_socket_local::socket (int af, int type, int protocol, int flags) +{ + SOCKET sock; + int ret; + + if (type != SOCK_STREAM && type != SOCK_DGRAM) + { + set_errno (EINVAL); + return -1; + } + if (protocol != 0) + { + set_errno (EPROTONOSUPPORT); + return -1; + } + sock = ::socket (AF_INET, type, protocol); + if (sock == INVALID_SOCKET) + { + set_winsock_errno (); + return -1; + } + ret = set_socket_handle (sock, af, type, flags); + if (ret < 0) + ::closesocket (sock); + return ret; +} + +int +fhandler_socket_local::socketpair (int af, int type, int protocol, int flags, + fhandler_socket *_fh_out) +{ + SOCKET insock = INVALID_SOCKET; + SOCKET outsock = INVALID_SOCKET; + SOCKET sock = INVALID_SOCKET; + struct sockaddr_in sock_in, sock_out; + int len; + fhandler_socket_local *fh_out = reinterpret_cast<fhandler_socket_local *> + (_fh_out); + + if (type != SOCK_STREAM && type != SOCK_DGRAM) + { + set_errno (EINVAL); + return -1; + } + if (protocol != 0) + { + set_errno (EPROTONOSUPPORT); + return -1; + } + /* create listening socket */ + sock = ::socket (AF_INET, type, 0); + if (sock == INVALID_SOCKET) + { + set_winsock_errno (); + goto err; + } + /* bind to unused port */ + sock_in.sin_family = AF_INET; + sock_in.sin_port = 0; + sock_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + if (::bind (sock, (struct sockaddr *) &sock_in, sizeof (sock_in)) < 0) + { + set_winsock_errno (); + goto err; + } + /* fetch socket name */ + len = sizeof (sock_in); + if (::getsockname (sock, (struct sockaddr *) &sock_in, &len) < 0) + { + set_winsock_errno (); + goto err; + } + /* on stream sockets, create listener */ + if (type == SOCK_STREAM && ::listen (sock, 2) < 0) + { + set_winsock_errno (); + goto err; + } + /* create connecting socket */ + outsock = ::socket (AF_INET, type, 0); + if (outsock == INVALID_SOCKET) + { + set_winsock_errno (); + goto err; + } + /* on datagram sockets, bind connecting socket */ + if (type == SOCK_DGRAM) + { + sock_out.sin_family = AF_INET; + sock_out.sin_port = 0; + sock_out.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + if (::bind (outsock, (struct sockaddr *) &sock_out, + sizeof (sock_out)) < 0) + { + set_winsock_errno (); + goto err; + } + /* ...and fetch name */ + len = sizeof (sock_out); + if (::getsockname (outsock, (struct sockaddr *) &sock_out, &len) < 0) + { + set_winsock_errno (); + goto err; + } + } + sock_in.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + if (type == SOCK_DGRAM) + sock_out.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + /* connect */ + if (::connect (outsock, (struct sockaddr *) &sock_in, sizeof (sock_in)) < 0) + { + set_winsock_errno (); + goto err; + } + if (type == SOCK_STREAM) + { + /* on stream sockets, accept connection and close listener */ + len = sizeof (sock_in); + insock = ::accept (sock, (struct sockaddr *) &sock_in, &len); + if (insock == INVALID_SOCKET) + { + set_winsock_errno (); + goto err; + } + ::closesocket (sock); + } + else + { + /* on datagram sockets, connect vice versa */ + if (::connect (sock, (struct sockaddr *) &sock_out, + sizeof (sock_out)) < 0) + { + set_winsock_errno (); + goto err; + } + insock = sock; + } + sock = INVALID_SOCKET; + + /* postprocessing */ + connect_state (connected); + fh_out->connect_state (connected); + if (af == AF_LOCAL && type == SOCK_STREAM) + { + af_local_set_sockpair_cred (); + fh_out->af_local_set_sockpair_cred (); + } + if (set_socket_handle (insock, af, type, flags) < 0 + || fh_out->set_socket_handle (outsock, af, type, flags) < 0) + goto err; + + return 0; + +err: + if (sock != INVALID_SOCKET) + ::closesocket (sock); + if (insock != INVALID_SOCKET) + ::closesocket (insock); + if (outsock != INVALID_SOCKET) + ::closesocket (outsock); + return -1; +} + +void +fhandler_socket_local::af_local_set_sockpair_cred () +{ + sec_pid = sec_peer_pid = getpid (); + sec_uid = sec_peer_uid = geteuid (); + sec_gid = sec_peer_gid = getegid (); +} + +void +fhandler_socket_local::af_local_setblocking (bool &async, bool &nonblocking) +{ + async = async_io (); + nonblocking = is_nonblocking (); + if (async) + { + WSAAsyncSelect (get_socket (), winmsg, 0, 0); + WSAEventSelect (get_socket (), wsock_evt, EVENT_MASK); + } + set_nonblocking (false); + async_io (false); +} + +void +fhandler_socket_local::af_local_unsetblocking (bool async, bool nonblocking) +{ + if (nonblocking) + set_nonblocking (true); + if (async) + { + WSAAsyncSelect (get_socket (), winmsg, WM_ASYNCIO, ASYNC_MASK); + async_io (true); + } +} + +bool +fhandler_socket_local::af_local_recv_secret () +{ + int out[4] = { 0, 0, 0, 0 }; + int rest = sizeof out; + char *ptr = (char *) out; + while (rest > 0) + { + int ret = recvfrom (ptr, rest, 0, NULL, NULL); + if (ret <= 0) + break; + rest -= ret; + ptr += ret; + } + if (rest == 0) + { + debug_printf ("Received af_local secret: %08x-%08x-%08x-%08x", + out[0], out[1], out[2], out[3]); + if (out[0] != connect_secret[0] || out[1] != connect_secret[1] + || out[2] != connect_secret[2] || out[3] != connect_secret[3]) + { + debug_printf ("Receiving af_local secret mismatch"); + return false; + } + } + else + debug_printf ("Receiving af_local secret failed"); + return rest == 0; +} + +bool +fhandler_socket_local::af_local_send_secret () +{ + int rest = sizeof connect_secret; + char *ptr = (char *) connect_secret; + while (rest > 0) + { + int ret = sendto (ptr, rest, 0, NULL, 0); + if (ret <= 0) + break; + rest -= ret; + ptr += ret; + } + debug_printf ("Sending af_local secret %s", rest == 0 ? "succeeded" + : "failed"); + return rest == 0; +} + +bool +fhandler_socket_local::af_local_recv_cred () +{ + struct ucred out = { (pid_t) 0, (uid_t) -1, (gid_t) -1 }; + int rest = sizeof out; + char *ptr = (char *) &out; + while (rest > 0) + { + int ret = recvfrom (ptr, rest, 0, NULL, NULL); + if (ret <= 0) + break; + rest -= ret; + ptr += ret; + } + if (rest == 0) + { + debug_printf ("Received eid credentials: pid: %d, uid: %d, gid: %d", + out.pid, out.uid, out.gid); + sec_peer_pid = out.pid; + sec_peer_uid = out.uid; + sec_peer_gid = out.gid; + } + else + debug_printf ("Receiving eid credentials failed"); + return rest == 0; +} + +bool +fhandler_socket_local::af_local_send_cred () +{ + struct ucred in = { sec_pid, sec_uid, sec_gid }; + int rest = sizeof in; + char *ptr = (char *) ∈ + while (rest > 0) + { + int ret = sendto (ptr, rest, 0, NULL, 0); + if (ret <= 0) + break; + rest -= ret; + ptr += ret; + } + if (rest == 0) + debug_printf ("Sending eid credentials succeeded"); + else + debug_printf ("Sending eid credentials failed"); + return rest == 0; +} + +int +fhandler_socket_local::af_local_connect () +{ + bool orig_async_io, orig_is_nonblocking; + + if (get_socket_type () != SOCK_STREAM) + return 0; + + debug_printf ("af_local_connect called, no_getpeereid=%d", no_getpeereid ()); + if (no_getpeereid ()) + return 0; + + af_local_setblocking (orig_async_io, orig_is_nonblocking); + if (!af_local_send_secret () || !af_local_recv_secret () + || !af_local_send_cred () || !af_local_recv_cred ()) + { + debug_printf ("accept from unauthorized server"); + ::shutdown (get_socket (), SD_BOTH); + WSASetLastError (WSAECONNREFUSED); + return -1; + } + af_local_unsetblocking (orig_async_io, orig_is_nonblocking); + return 0; +} + +int +fhandler_socket_local::af_local_accept () +{ + bool orig_async_io, orig_is_nonblocking; + + debug_printf ("af_local_accept called, no_getpeereid=%d", no_getpeereid ()); + if (no_getpeereid ()) + return 0; + + af_local_setblocking (orig_async_io, orig_is_nonblocking); + if (!af_local_recv_secret () || !af_local_send_secret () + || !af_local_recv_cred () || !af_local_send_cred ()) + { + debug_printf ("connect from unauthorized client"); + ::shutdown (get_socket (), SD_BOTH); + ::closesocket (get_socket ()); + WSASetLastError (WSAECONNABORTED); + return -1; + } + af_local_unsetblocking (orig_async_io, orig_is_nonblocking); + return 0; +} + +int +fhandler_socket_local::af_local_set_no_getpeereid () +{ + if (get_addr_family () != AF_LOCAL || get_socket_type () != SOCK_STREAM) + { + set_errno (EINVAL); + return -1; + } + if (connect_state () != unconnected) + { + set_errno (EALREADY); + return -1; + } + + debug_printf ("no_getpeereid set"); + no_getpeereid (true); + return 0; +} + +void +fhandler_socket_local::af_local_set_cred () +{ + sec_pid = getpid (); + sec_uid = geteuid (); + sec_gid = getegid (); + sec_peer_pid = (pid_t) 0; + sec_peer_uid = (uid_t) -1; + sec_peer_gid = (gid_t) -1; +} + +void +fhandler_socket_local::af_local_copy (fhandler_socket_local *sock) +{ + sock->connect_secret[0] = connect_secret[0]; + sock->connect_secret[1] = connect_secret[1]; + sock->connect_secret[2] = connect_secret[2]; + sock->connect_secret[3] = connect_secret[3]; + sock->sec_pid = sec_pid; + sock->sec_uid = sec_uid; + sock->sec_gid = sec_gid; + sock->sec_peer_pid = sec_peer_pid; + sock->sec_peer_uid = sec_peer_uid; + sock->sec_peer_gid = sec_peer_gid; + sock->no_getpeereid (no_getpeereid ()); +} + +void +fhandler_socket_local::af_local_set_secret (char *buf) +{ + if (!RtlGenRandom (connect_secret, sizeof (connect_secret))) + bzero ((char*) connect_secret, sizeof (connect_secret)); + __small_sprintf (buf, "%08x-%08x-%08x-%08x", + connect_secret [0], connect_secret [1], + connect_secret [2], connect_secret [3]); +} + +int +fhandler_socket_local::dup (fhandler_base *child, int flags) +{ + if (get_flags () & O_PATH) + /* We're viewing the socket as a disk file, but fhandler_base::dup + suffices here. */ + return fhandler_base::dup (child, flags); + + fhandler_socket_local *fhs = (fhandler_socket_local *) child; + fhs->set_sun_path (get_sun_path ()); + fhs->set_peer_sun_path (get_peer_sun_path ()); + return fhandler_socket_wsock::dup (child, flags); +} + +int +fhandler_socket_local::open (int flags, mode_t mode) +{ + /* We don't support opening sockets unless O_PATH is specified. */ + if (flags & O_PATH) + return open_fs (flags, mode); + + set_errno (EOPNOTSUPP); + return 0; +} + +int +fhandler_socket_local::close () +{ + if (get_flags () & O_PATH) + return fhandler_base::close (); + else + return fhandler_socket_wsock::close (); +} + +int +fhandler_socket_local::fcntl (int cmd, intptr_t arg) +{ + if (get_flags () & O_PATH) + /* We're viewing the socket as a disk file, but + fhandler_base::fcntl suffices here. */ + return fhandler_base::fcntl (cmd, arg); + else + return fhandler_socket_wsock::fcntl (cmd, arg); +} + +int +fhandler_socket_local::fstat (struct stat *buf) +{ + if (!dev ().isfs ()) + /* fstat called on a socket. */ + return fhandler_socket_wsock::fstat (buf); + + /* stat/lstat on a socket file or fstat on a socket opened w/ O_PATH. */ + int res = fhandler_base::fstat_fs (buf); + if (!res) + { + buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFSOCK; + buf->st_size = 0; + } + return res; +} + +int +fhandler_socket_local::fstatvfs (struct statvfs *sfs) +{ + if (!dev ().isfs ()) + /* fstatvfs called on a socket. */ + return fhandler_socket_wsock::fstatvfs (sfs); + + /* statvfs on a socket file or fstatvfs on a socket opened w/ O_PATH. */ + if (get_flags () & O_PATH) + /* We already have a handle. */ + { + HANDLE h = get_handle (); + if (h) + return fstatvfs_by_handle (h, sfs); + } + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + return fh.fstatvfs (sfs); +} + +int +fhandler_socket_local::fchmod (mode_t newmode) +{ + if (!dev ().isfs ()) + /* fchmod called on a socket. */ + return fhandler_socket_wsock::fchmod (newmode); + + /* chmod on a socket file. [We won't get here if fchmod is called + on a socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + fh.get_device () = FH_FS; + return fh.fchmod (S_IFSOCK | adjust_socket_file_mode (newmode)); +} + +int +fhandler_socket_local::fchown (uid_t uid, gid_t gid) +{ + if (!dev ().isfs ()) + /* fchown called on a socket. */ + return fhandler_socket_wsock::fchown (uid, gid); + + /* chown/lchown on a socket file. [We won't get here if fchown is + called on a socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + return fh.fchown (uid, gid); +} + +int +fhandler_socket_local::facl (int cmd, int nentries, aclent_t *aclbufp) +{ + if (!dev ().isfs ()) + /* facl called on a socket. */ + return fhandler_socket_wsock::facl (cmd, nentries, aclbufp); + + /* facl on a socket file. [We won't get here if facl is called on a + socket opened w/ O_PATH.] */ + fhandler_disk_file fh (pc); + return fh.facl (cmd, nentries, aclbufp); +} + +int +fhandler_socket_local::link (const char *newpath) +{ + if (!dev ().isfs ()) + /* linkat w/ AT_EMPTY_PATH called on a socket not opened w/ O_PATH. */ + return fhandler_socket_wsock::link (newpath); + /* link on a socket file or linkat w/ AT_EMPTY_PATH called on a + socket opened w/ O_PATH. */ + fhandler_disk_file fh (pc); + if (get_flags () & O_PATH) + fh.set_handle (get_handle ()); + return fh.link (newpath); +} + +int +fhandler_socket_local::bind (const struct sockaddr *name, int namelen) +{ + int res = -1; + +#define un_addr ((struct sockaddr_un *) name) + struct sockaddr_in sin; + int len = namelen - offsetof (struct sockaddr_un, sun_path); + + /* Check that name is within bounds. Don't check if the string is + NUL-terminated, because there are projects out there which set + namelen to a value which doesn't cover the trailing NUL. */ + if (len <= 1 || (len = strnlen (un_addr->sun_path, len)) > UNIX_PATH_MAX) + { + set_errno (len <= 1 ? (len == 1 ? ENOENT : EINVAL) : ENAMETOOLONG); + return -1; + } + /* Copy over the sun_path string into a buffer big enough to add a + trailing NUL. */ + char sun_path[len + 1]; + strncpy (sun_path, un_addr->sun_path, len); + sun_path[len] = '\0'; + + /* This isn't entirely foolproof, but we check first if the file exists + so we can return with EADDRINUSE before having bound the socket. + This allows an application to call bind again on the same socket using + another filename. If we bind first, the application will not be able + to call bind successfully ever again. */ + path_conv pc (sun_path, PC_SYM_FOLLOW); + if (pc.error) + { + set_errno (pc.error); + return -1; + } + if (pc.exists ()) + { + set_errno (EADDRINUSE); + return -1; + } + + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + if (::bind (get_socket (), (sockaddr *) &sin, len = sizeof sin)) + { + syscall_printf ("AF_LOCAL: bind failed"); + set_winsock_errno (); + return -1; + } + if (::getsockname (get_socket (), (sockaddr *) &sin, &len)) + { + syscall_printf ("AF_LOCAL: getsockname failed"); + set_winsock_errno (); + return -1; + } + + sin.sin_port = ntohs (sin.sin_port); + debug_printf ("AF_LOCAL: socket bound to port %u", sin.sin_port); + + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + DWORD fattr = FILE_ATTRIBUTE_SYSTEM; + if (!pc.has_acls () + && !(mode & ~cygheap->umask & (S_IWUSR | S_IWGRP | S_IWOTH))) + fattr |= FILE_ATTRIBUTE_READONLY; + SECURITY_ATTRIBUTES sa = sec_none_nih; + NTSTATUS status; + HANDLE fh; + OBJECT_ATTRIBUTES attr; + IO_STATUS_BLOCK io; + ULONG access = DELETE | FILE_GENERIC_WRITE; + + /* If the filesystem supports ACLs, we will overwrite the DACL after the + call to NtCreateFile. This requires a handle with READ_CONTROL and + WRITE_DAC access, otherwise get_file_sd and set_file_sd both have to + open the file again. + FIXME: On remote NTFS shares open sometimes fails because even the + creator of the file doesn't have the right to change the DACL. + I don't know what setting that is or how to recognize such a share, + so for now we don't request WRITE_DAC on remote drives. */ + if (pc.has_acls () && !pc.isremote ()) + access |= READ_CONTROL | WRITE_DAC | WRITE_OWNER; + + status = NtCreateFile (&fh, access, pc.get_object_attr (attr, sa), &io, + NULL, fattr, 0, FILE_CREATE, + FILE_NON_DIRECTORY_FILE + | FILE_SYNCHRONOUS_IO_NONALERT + | FILE_OPEN_FOR_BACKUP_INTENT, + NULL, 0); + if (!NT_SUCCESS (status)) + { + if (io.Information == FILE_EXISTS) + set_errno (EADDRINUSE); + else + __seterrno_from_nt_status (status); + } + else + { + if (pc.has_acls ()) + set_created_file_access (fh, pc, mode); + char buf[sizeof (SOCKET_COOKIE) + 80]; + __small_sprintf (buf, "%s%u %c ", SOCKET_COOKIE, sin.sin_port, + get_socket_type () == SOCK_STREAM ? 's' + : get_socket_type () == SOCK_DGRAM ? 'd' : '-'); + af_local_set_secret (strchr (buf, '\0')); + DWORD blen = strlen (buf) + 1; + status = NtWriteFile (fh, NULL, NULL, NULL, &io, buf, blen, NULL, 0); + if (!NT_SUCCESS (status)) + { + __seterrno_from_nt_status (status); + FILE_DISPOSITION_INFORMATION fdi = { TRUE }; + status = NtSetInformationFile (fh, &io, &fdi, sizeof fdi, + FileDispositionInformation); + if (!NT_SUCCESS (status)) + debug_printf ("Setting delete dispostion failed, status = %y", + status); + } + else + { + set_sun_path (sun_path); + res = 0; + } + NtClose (fh); + } +#undef un_addr + + return res; +} + +int +fhandler_socket_local::connect (const struct sockaddr *name, int namelen) +{ + struct sockaddr_storage sst; + int type = 0; + bool reset = (name->sa_family == AF_UNSPEC + && get_socket_type () == SOCK_DGRAM); + + if (reset) + { + if (connect_state () == unconnected) + return 0; + /* To reset a connected DGRAM socket, call Winsock's connect + function with the address member of the sockaddr structure + filled with zeroes. */ + memset (&sst, 0, sizeof sst); + sst.ss_family = get_addr_family (); + } + else if (get_inet_addr_local (name, namelen, &sst, &namelen, &type, + connect_secret) == SOCKET_ERROR) + return SOCKET_ERROR; + + if (get_socket_type () != type && !reset) + { + WSASetLastError (WSAEPROTOTYPE); + set_winsock_errno (); + return SOCKET_ERROR; + } + + if (reset) + set_peer_sun_path (NULL); + else + set_peer_sun_path (name->sa_data); + + /* Don't move af_local_set_cred into af_local_connect which may be called + via select, possibly running under another identity. Call early here, + because af_local_connect is called in wait_for_events. */ + if (get_socket_type () == SOCK_STREAM) + af_local_set_cred (); + + /* Initialize connect state to "connect_pending". In the SOCK_STREAM + case, the state is ultimately set to "connected" or "connect_failed" in + wait_for_events when the FD_CONNECT event occurs. Note that the + underlying OS sockets are always non-blocking in this case and a + successfully initiated non-blocking Winsock connect always returns + WSAEWOULDBLOCK. Thus it's safe to rely on event handling. For DGRAM + sockets, however, connect can return immediately. + + Check for either unconnected or connect_failed since in both cases it's + allowed to retry connecting the socket. It's also ok (albeit ugly) to + call connect to check if a previous non-blocking connect finished. + + Set connect_state before calling connect, otherwise a race condition with + an already running select or poll might occur. */ + if (connect_state () == unconnected || connect_state () == connect_failed) + connect_state (connect_pending); + + int res = ::connect (get_socket (), (struct sockaddr *) &sst, namelen); + if (!res) + { + if (reset) + connect_state (unconnected); + else + connect_state (connected); + } + else if (!is_nonblocking () + && res == SOCKET_ERROR + && WSAGetLastError () == WSAEWOULDBLOCK) + res = wait_for_events (FD_CONNECT | FD_CLOSE, 0); + + if (res) + { + DWORD err = WSAGetLastError (); + + /* Some applications use the ugly technique to check if a non-blocking + connect succeeded by calling connect again, until it returns EISCONN. + This circumvents the event handling and connect_state is never set. + Thus we check for this situation here. */ + if (err == WSAEISCONN) + connect_state (connected); + /* Winsock returns WSAEWOULDBLOCK if the non-blocking socket cannot be + conected immediately. Convert to POSIX/Linux compliant EINPROGRESS. */ + else if (is_nonblocking () && err == WSAEWOULDBLOCK) + WSASetLastError (WSAEINPROGRESS); + /* Winsock returns WSAEINVAL if the socket is already a listener. + Convert to POSIX/Linux compliant EISCONN. */ + else if (err == WSAEINVAL && connect_state () == listener) + WSASetLastError (WSAEISCONN); + /* Any other error except WSAEALREADY means the connect failed. */ + else if (connect_state () == connect_pending && err != WSAEALREADY) + connect_state (connect_failed); + set_winsock_errno (); + } + + return res; +} + +int +fhandler_socket_local::listen (int backlog) +{ + int res = ::listen (get_socket (), backlog); + if (res && WSAGetLastError () == WSAEINVAL) + { + /* It's perfectly valid to call listen on an unbound INET socket. + In this case the socket is automatically bound to an unused + port number, listening on all interfaces. On WinSock, listen + fails with WSAEINVAL when it's called on an unbound socket. + So we have to bind manually here to have POSIX semantics. */ + if (get_addr_family () == AF_INET) + { + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = 0; + sin.sin_addr.s_addr = INADDR_ANY; + if (!::bind (get_socket (), (struct sockaddr *) &sin, sizeof sin)) + res = ::listen (get_socket (), backlog); + } + else if (get_addr_family () == AF_INET6) + { + struct sockaddr_in6 sin6; + memset (&sin6, 0, sizeof sin6); + sin6.sin6_family = AF_INET6; + if (!::bind (get_socket (), (struct sockaddr *) &sin6, sizeof sin6)) + res = ::listen (get_socket (), backlog); + } + } + if (!res) + { + if (get_addr_family () == AF_LOCAL && get_socket_type () == SOCK_STREAM) + af_local_set_cred (); + connect_state (listener); /* gets set to connected on accepted socket. */ + } + else + set_winsock_errno (); + return res; +} + +int +fhandler_socket_local::accept4 (struct sockaddr *peer, int *len, int flags) +{ + int ret = -1; + /* Allows NULL peer and len parameters. */ + struct sockaddr_storage lpeer; + int llen = sizeof (struct sockaddr_storage); + + /* Windows event handling does not check for the validity of the desired + flags so we have to do it here. */ + if (connect_state () != listener) + { + WSASetLastError (WSAEINVAL); + set_winsock_errno (); + return -1; + } + + SOCKET res = INVALID_SOCKET; + while (!(res = wait_for_events (FD_ACCEPT | FD_CLOSE, 0)) + && (res = ::accept (get_socket (), (struct sockaddr *) &lpeer, &llen)) + == INVALID_SOCKET + && WSAGetLastError () == WSAEWOULDBLOCK) + ; + if (res == INVALID_SOCKET) + set_winsock_errno (); + else + { + cygheap_fdnew fd; + + if (fd >= 0) + { + fhandler_socket_local *sock = (fhandler_socket_local *) + build_fh_dev (dev ()); + if (sock && sock->set_socket_handle (res, get_addr_family (), + get_socket_type (), + get_socket_flags ()) == 0) + { + sock->async_io (false); /* set_socket_handle disables async. */ + sock->set_sun_path (get_sun_path ()); + sock->set_peer_sun_path (get_peer_sun_path ()); + if (get_socket_type () == SOCK_STREAM) + { + /* Don't forget to copy credentials from accepting + socket to accepted socket and start transaction + on accepted socket! */ + af_local_copy (sock); + ret = sock->af_local_accept (); + if (ret == -1) + { + delete sock; + set_winsock_errno (); + return -1; + } + } + /* No locking necessary at this point. */ + sock->wsock_events->events = wsock_events->events | FD_WRITE; + sock->wsock_events->owner = wsock_events->owner; + sock->connect_state (connected); + fd = sock; + if (fd <= 2) + set_std_handle (fd); + ret = fd; + if (peer) + { + /* FIXME: Right now we have no way to determine the + bound socket name of the peer's socket. For now + we just fake an unbound socket on the other side. */ + static struct sockaddr_un un = { AF_LOCAL, "" }; + memcpy (peer, &un, MIN (*len, (int) sizeof (un.sun_family))); + *len = (int) sizeof (un.sun_family); + } + } + else + delete sock; + } + if (ret == -1) + ::closesocket (res); + } + return ret; +} + +int +fhandler_socket_local::getsockname (struct sockaddr *name, int *namelen) +{ + struct sockaddr_un sun; + + sun.sun_family = AF_LOCAL; + sun.sun_path[0] = '\0'; + if (get_sun_path ()) + strncat (sun.sun_path, get_sun_path (), UNIX_PATH_MAX - 1); + memcpy (name, &sun, MIN (*namelen, (int) SUN_LEN (&sun) + 1)); + *namelen = (int) SUN_LEN (&sun) + (get_sun_path () ? 1 : 0); + return 0; +} + +int +fhandler_socket_local::getpeername (struct sockaddr *name, int *namelen) +{ + /* Always use a local big enough buffer and truncate later as necessary + per POSIX. WinSock unfortunately only returns WSAEFAULT if the buffer + is too small. */ + struct sockaddr_storage sock; + int len = sizeof sock; + int res = ::getpeername (get_socket (), (struct sockaddr *) &sock, &len); + if (res) + set_winsock_errno (); + else + { + struct sockaddr_un sun; + memset (&sun, 0, sizeof sun); + sun.sun_family = AF_LOCAL; + sun.sun_path[0] = '\0'; + if (get_peer_sun_path ()) + strncat (sun.sun_path, get_peer_sun_path (), UNIX_PATH_MAX - 1); + memcpy (name, &sun, MIN (*namelen, (int) SUN_LEN (&sun) + 1)); + *namelen = (int) SUN_LEN (&sun) + (get_peer_sun_path () ? 1 : 0); + } + return res; +} + +ssize_t +fhandler_socket_local::recv_internal (LPWSAMSG wsamsg, bool use_recvmsg) +{ + ssize_t res = 0; + DWORD ret = 0, wret; + int evt_mask = FD_READ; + LPWSABUF &wsabuf = wsamsg->lpBuffers; + ULONG &wsacnt = wsamsg->dwBufferCount; + static NO_COPY LPFN_WSARECVMSG WSARecvMsg; + int orig_namelen = wsamsg->namelen; + + /* CV 2014-10-26: Do not check for the connect_state at this point. In + certain scenarios there's no way to check the connect state reliably. + Example (hexchat): Parent process creates socket, forks, child process + calls connect, parent process calls read. Even if the event handling + allows to check for FD_CONNECT in the parent, there is always yet another + scenario we can easily break. */ + + DWORD wait_flags = wsamsg->dwFlags; + bool waitall = !!(wait_flags & MSG_WAITALL); + + /* Out-of-band data not supported by AF_LOCAL */ + if (wsamsg->dwFlags & MSG_OOB) + { + set_errno (EOPNOTSUPP); + return SOCKET_ERROR; + } + + wsamsg->dwFlags &= (MSG_PEEK | MSG_DONTROUTE); + if (use_recvmsg) + { + if (!WSARecvMsg + && get_ext_funcptr (get_socket (), &WSARecvMsg) == SOCKET_ERROR) + { + if (wsamsg->Control.len > 0) + { + set_winsock_errno (); + return SOCKET_ERROR; + } + use_recvmsg = false; + } + else /* Only MSG_PEEK is supported by WSARecvMsg. */ + wsamsg->dwFlags &= MSG_PEEK; + } + if (waitall) + { + if (get_socket_type () != SOCK_STREAM) + { + WSASetLastError (WSAEOPNOTSUPP); + set_winsock_errno (); + return SOCKET_ERROR; + } + if (is_nonblocking () || (wsamsg->dwFlags & MSG_PEEK)) + waitall = false; + } + + /* Note: Don't call WSARecvFrom(MSG_PEEK) without actually having data + waiting in the buffers, otherwise the event handling gets messed up + for some reason. */ + while (!(res = wait_for_events (evt_mask | FD_CLOSE, wait_flags)) + || saw_shutdown_read ()) + { + DWORD dwFlags = wsamsg->dwFlags; + if (use_recvmsg) + res = WSARecvMsg (get_socket (), wsamsg, &wret, NULL, NULL); + /* This is working around a really weird problem in WinSock. + + Assume you create a socket, fork the process (thus duplicating + the socket), connect the socket in the child, then call recv + on the original socket handle in the parent process. + In this scenario, calls to WinSock's recvfrom and WSARecvFrom + in the parent will fail with WSAEINVAL, regardless whether both + address parameters, name and namelen, are NULL or point to valid + storage. However, calls to recv and WSARecv succeed as expected. + Per MSDN, WSAEINVAL in the context of recv means "The socket has not + been bound". It is as if the recvfrom functions test if the socket + is bound locally, but in the parent process, WinSock doesn't know + about that and fails, while the same test is omitted in the recv + functions. + + This also covers another weird case: WinSock returns WSAEFAULT if + namelen is a valid pointer while name is NULL. Both parameters are + ignored for TCP sockets, so this only occurs when using UDP socket. */ + else if (!wsamsg->name || get_socket_type () == SOCK_STREAM) + res = WSARecv (get_socket (), wsabuf, wsacnt, &wret, &dwFlags, + NULL, NULL); + else + res = WSARecvFrom (get_socket (), wsabuf, wsacnt, &wret, + &dwFlags, wsamsg->name, &wsamsg->namelen, + NULL, NULL); + if (!res) + { + ret += wret; + if (!waitall) + break; + while (wret && wsacnt) + { + if (wsabuf->len > wret) + { + wsabuf->len -= wret; + wsabuf->buf += wret; + wret = 0; + } + else + { + wret -= wsabuf->len; + ++wsabuf; + --wsacnt; + } + } + if (!wsacnt) + break; + } + else if (WSAGetLastError () != WSAEWOULDBLOCK) + break; + } + + if (res) + { + /* According to SUSv3, errno isn't set in that case and no error + condition is returned. */ + if (WSAGetLastError () == WSAEMSGSIZE) + ret += wret; + else if (!ret) + { + /* ESHUTDOWN isn't defined for recv in SUSv3. Simply EOF is returned + in this case. */ + if (WSAGetLastError () == WSAESHUTDOWN) + ret = 0; + else + { + set_winsock_errno (); + return SOCKET_ERROR; + } + } + } + + if (wsamsg->name != NULL && orig_namelen >= (int) sizeof (sa_family_t)) + { + /* WSARecvFrom copied the sockaddr_in block to wsamsg->name. We have to + overwrite it with a sockaddr_un block. For datagram sockets we + generate a sockaddr_un with a filename analogue to abstract socket + names under Linux. See `man 7 unix' under Linux for a description. */ + sockaddr_un *un = (sockaddr_un *) wsamsg->name; + un->sun_family = AF_LOCAL; + int len = orig_namelen - offsetof (struct sockaddr_un, sun_path); + if (len > 0) + { + if (get_socket_type () == SOCK_DGRAM) + { + if (len >= 7) + { + __small_sprintf (un->sun_path + 1, "d%04x", + ((struct sockaddr_in *) wsamsg->name)->sin_port); + wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + 7; + } + else + wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + 1; + un->sun_path[0] = '\0'; + } + else if (!get_peer_sun_path ()) + wsamsg->namelen = sizeof (sa_family_t); + else + { + memset (un->sun_path, 0, len); + strncpy (un->sun_path, get_peer_sun_path (), len); + if (un->sun_path[len - 1] == '\0') + len = strlen (un->sun_path) + 1; + if (len > UNIX_PATH_MAX) + len = UNIX_PATH_MAX; + wsamsg->namelen = offsetof (struct sockaddr_un, sun_path) + len; + } + } + } + + return ret; +} + +ssize_t +fhandler_socket_local::sendto (const void *in_ptr, size_t len, int flags, + const struct sockaddr *to, int tolen) +{ + char *ptr = (char *) in_ptr; + struct sockaddr_storage sst; + + /* Out-of-band data not supported by AF_LOCAL */ + if (flags & MSG_OOB) + { + set_errno (EOPNOTSUPP); + return SOCKET_ERROR; + } + + if (to && get_inet_addr_local (to, tolen, &sst, &tolen) == SOCKET_ERROR) + return SOCKET_ERROR; + + /* size_t is 64 bit, but the len member in WSABUF is 32 bit. + Split buffer if necessary. */ + DWORD bufcnt = len / UINT32_MAX + ((!len || (len % UINT32_MAX)) ? 1 : 0); + WSABUF wsabuf[bufcnt]; + WSAMSG wsamsg = { to ? (struct sockaddr *) &sst : NULL, tolen, + wsabuf, bufcnt, + { 0, NULL }, + 0 }; + /* Don't use len as loop condition, it could be 0. */ + for (WSABUF *wsaptr = wsabuf; bufcnt--; ++wsaptr) + { + wsaptr->len = MIN (len, UINT32_MAX); + wsaptr->buf = ptr; + len -= wsaptr->len; + ptr += wsaptr->len; + } + return send_internal (&wsamsg, flags); +} + +ssize_t +fhandler_socket_local::sendmsg (const struct msghdr *msg, int flags) +{ + /* TODO: Descriptor passing on AF_LOCAL sockets. */ + + struct sockaddr_storage sst; + int len = 0; + + /* Out-of-band data not supported by AF_LOCAL */ + if (flags & MSG_OOB) + { + set_errno (EOPNOTSUPP); + return SOCKET_ERROR; + } + + if (msg->msg_name + && get_inet_addr_local ((struct sockaddr *) msg->msg_name, + msg->msg_namelen, &sst, &len) == SOCKET_ERROR) + return SOCKET_ERROR; + + WSABUF wsabuf[msg->msg_iovlen]; + WSABUF *wsaptr = wsabuf; + const struct iovec *iovptr = msg->msg_iov; + for (int i = 0; i < msg->msg_iovlen; ++i) + { + wsaptr->len = iovptr->iov_len; + (wsaptr++)->buf = (char *) (iovptr++)->iov_base; + } + /* Disappointing but true: Even if WSASendMsg is supported, it's only + supported for datagram and raw sockets. */ + DWORD controllen = (DWORD) (get_socket_type () == SOCK_STREAM + || get_addr_family () == AF_LOCAL + ? 0 : msg->msg_controllen); + WSAMSG wsamsg = { msg->msg_name ? (struct sockaddr *) &sst : NULL, len, + wsabuf, (DWORD) msg->msg_iovlen, + { controllen, (char *) msg->msg_control }, + 0 }; + return send_internal (&wsamsg, flags); +} + +void +fhandler_socket_local::set_sun_path (const char *path) +{ + sun_path = path ? cstrdup (path) : NULL; +} + +void +fhandler_socket_local::set_peer_sun_path (const char *path) +{ + peer_sun_path = path ? cstrdup (path) : NULL; +} + +int +fhandler_socket_local::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid) +{ + if (get_socket_type () != SOCK_STREAM) + { + set_errno (EINVAL); + return -1; + } + if (no_getpeereid ()) + { + set_errno (ENOTSUP); + return -1; + } + if (connect_state () != connected) + { + set_errno (ENOTCONN); + return -1; + } + + __try + { + if (pid) + *pid = sec_peer_pid; + if (euid) + *euid = sec_peer_uid; + if (egid) + *egid = sec_peer_gid; + return 0; + } + __except (EFAULT) {} + __endtry + return -1; +} + +int +fhandler_socket_local::setsockopt (int level, int optname, const void *optval, + socklen_t optlen) +{ + int ret = -1; + + /* Preprocessing setsockopt. */ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_PEERCRED: + /* Switch off the AF_LOCAL handshake and thus SO_PEERCRED handling + for AF_LOCAL/SOCK_STREAM sockets. This allows to handle special + situations in which connect is called before a listening socket + accepts connections. + FIXME: In the long run we should find a more generic solution + which doesn't require a blocking handshake in accept/connect + to exchange SO_PEERCRED credentials. */ + /* Temporary: Allow SO_PEERCRED to only be zeroed. Two ways to + accomplish this: pass NULL,0 for optval,optlen; or pass the + address,length of an '(int) 0' set up by the caller. */ + if ((!optval && !optlen) || + (optlen == (socklen_t) sizeof (int) && !*(int *) optval)) + ret = af_local_set_no_getpeereid (); + else + set_errno (EINVAL); + return ret; + + case SO_REUSEADDR: + saw_reuseaddr (*(int *) optval); + return 0; + + case SO_RCVTIMEO: + case SO_SNDTIMEO: + if (optlen < (socklen_t) sizeof (struct timeval)) + { + set_errno (EINVAL); + return ret; + } + if (timeval_to_ms ((struct timeval *) optval, + (optname == SO_RCVTIMEO) ? rcvtimeo () + : sndtimeo ())) + return 0; + set_errno (EDOM); + return -1; + + case SO_DEBUG: + case SO_RCVBUF: + case SO_RCVLOWAT: + case SO_SNDBUF: + case SO_SNDLOWAT: + break; + + default: + /* AF_LOCAL sockets simply ignore all other SOL_SOCKET options. */ + return 0; + } + break; + + default: + set_errno (ENOPROTOOPT); + return -1; + } + + /* Call Winsock setsockopt */ + ret = ::setsockopt (get_socket (), level, optname, (const char *) optval, + optlen); + if (ret == SOCKET_ERROR) + { + set_winsock_errno (); + return ret; + } + + if (optlen == (socklen_t) sizeof (int)) + debug_printf ("setsockopt optval=%x", *(int *) optval); + + /* Postprocessing setsockopt, setting fhandler_socket members, etc. */ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_RCVBUF: + rmem (*(int *) optval); + break; + + case SO_SNDBUF: + wmem (*(int *) optval); + break; + + default: + break; + } + break; + + default: + break; + } + + return ret; +} + +int +fhandler_socket_local::getsockopt (int level, int optname, const void *optval, + socklen_t *optlen) +{ + int ret = -1; + + /* Preprocessing getsockopt.*/ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_PEERCRED: + { + struct ucred *cred = (struct ucred *) optval; + + if (*optlen < (socklen_t) sizeof *cred) + { + set_errno (EINVAL); + return ret; + } + ret = getpeereid (&cred->pid, &cred->uid, &cred->gid); + if (!ret) + *optlen = (socklen_t) sizeof *cred; + return ret; + } + + case SO_REUSEADDR: + { + unsigned int *reuseaddr = (unsigned int *) optval; + + if (*optlen < (socklen_t) sizeof *reuseaddr) + { + set_errno (EINVAL); + return -1; + } + *reuseaddr = saw_reuseaddr(); + *optlen = (socklen_t) sizeof *reuseaddr; + return 0; + } + + case SO_RCVTIMEO: + case SO_SNDTIMEO: + { + struct timeval *time_out = (struct timeval *) optval; + + if (*optlen < (socklen_t) sizeof *time_out) + { + set_errno (EINVAL); + return ret; + } + DWORD ms = (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo (); + if (ms == 0 || ms == INFINITE) + { + time_out->tv_sec = 0; + time_out->tv_usec = 0; + } + else + { + time_out->tv_sec = ms / MSPERSEC; + time_out->tv_usec = ((ms % MSPERSEC) * USPERSEC) / MSPERSEC; + } + *optlen = (socklen_t) sizeof *time_out; + return 0; + } + + case SO_TYPE: + { + unsigned int *type = (unsigned int *) optval; + *type = get_socket_type (); + *optlen = (socklen_t) sizeof *type; + return 0; + } + + case SO_ACCEPTCONN: + case SO_DEBUG: + case SO_ERROR: + case SO_RCVBUF: + case SO_RCVLOWAT: + case SO_SNDBUF: + case SO_SNDLOWAT: + break; + + /* AF_LOCAL sockets simply ignore all other SOL_SOCKET options. */ + + case SO_LINGER: + { + struct linger *linger = (struct linger *) optval; + memset (linger, 0, sizeof *linger); + *optlen = (socklen_t) sizeof *linger; + return 0; + } + + default: + { + unsigned int *val = (unsigned int *) optval; + *val = 0; + *optlen = (socklen_t) sizeof *val; + return 0; + } + } + break; + + default: + set_errno (ENOPROTOOPT); + return -1; + } + + /* Call Winsock getsockopt */ + ret = ::getsockopt (get_socket (), level, optname, (char *) optval, + (int *) optlen); + if (ret == SOCKET_ERROR) + { + set_winsock_errno (); + return ret; + } + + /* Postprocessing getsockopt, setting fhandler_socket members, etc. */ + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_ERROR: + { + int *e = (int *) optval; + debug_printf ("WinSock SO_ERROR = %d", *e); + *e = find_winsock_errno (*e); + } + break; + + default: + break; + } + break; + default: + break; + } + + return ret; +} |