/* fhandler_socket_unix.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. */ #include "winsup.h" #include #include "cygerrno.h" #include "security.h" #include "path.h" #include "fhandler.h" #include "dtable.h" #include "cygheap.h" #include #include "cygwin/version.h" #include "perprocess.h" #include "shared_info.h" #include "sigproc.h" #include "wininfo.h" #include #include #include #include #include "cygtls.h" #include #include "ntdll.h" #include "miscfuncs.h" #include "tls_pbuf.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 */ static int __unused get_inet_addr_unix (const struct sockaddr *in, int inlen, struct sockaddr_storage *out, int *outlen, int *type = NULL) { /* Check for abstract socket. */ if (inlen >= (int) sizeof (in->sa_family) + 7 && in->sa_data[0] == '\0' && in->sa_data[1] == 'd' && in->sa_data[6] == '\0') { /* TODO */ return 0; } path_conv pc (in->sa_data, PC_SYM_FOLLOW); if (pc.error) { set_errno (pc.error); return -1; } if (!pc.exists ()) { set_errno (ENOENT); return -1; } /* 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 -1; } yield (); } else if (!NT_SUCCESS (status)) { __seterrno_from_nt_status (status); return -1; } } 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 -1; } if (!(fbi.FileAttributes & FILE_ATTRIBUTE_SYSTEM)) { NtClose (fh); set_errno (EBADF); return -1; } /* 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)) { #if 0 /* TODO */ struct sockaddr_in sin; char ctype; sin.sin_family = AF_INET; if (strncmp (buf, SOCKET_COOKIE, strlen (SOCKET_COOKIE))) { set_errno (EBADF); return -1; } sscanf (buf + strlen (SOCKET_COOKIE), "%hu %c", &sin.sin_port, &ctype); 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); #endif return 0; } __seterrno_from_nt_status (status); return -1; } fhandler_socket_unix::fhandler_socket_unix () : sun_path (NULL), peer_sun_path (NULL) { set_cred (); } fhandler_socket_unix::~fhandler_socket_unix () { if (sun_path) cfree (sun_path); if (peer_sun_path) cfree (peer_sun_path); } void fhandler_socket_unix::set_sun_path (const char *path) { sun_path = path ? cstrdup (path) : NULL; } void fhandler_socket_unix::set_peer_sun_path (const char *path) { peer_sun_path = path ? cstrdup (path) : NULL; } void fhandler_socket_unix::set_cred () { sec_pid = getpid (); sec_uid = geteuid32 (); sec_gid = getegid32 (); sec_peer_pid = (pid_t) 0; sec_peer_uid = (uid_t) -1; sec_peer_gid = (gid_t) -1; } int fhandler_socket_unix::dup (fhandler_base *child, int flags) { fhandler_socket_unix *fhs = (fhandler_socket_unix *) child; fhs->set_sun_path (get_sun_path ()); fhs->set_peer_sun_path (get_peer_sun_path ()); return fhandler_socket::dup (child, flags); } int fhandler_socket_unix::socket (int af, int type, int protocol, int flags) { if (type != SOCK_STREAM && type != SOCK_DGRAM) { set_errno (EINVAL); return -1; } if (protocol != 0) { set_errno (EPROTONOSUPPORT); return -1; } set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::socketpair (int af, int type, int protocol, int flags, fhandler_socket *fh_out) { if (type != SOCK_STREAM && type != SOCK_DGRAM) { set_errno (EINVAL); return -1; } if (protocol != 0) { set_errno (EPROTONOSUPPORT); return -1; } set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::bind (const struct sockaddr *name, int namelen) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::listen (int backlog) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::accept4 (struct sockaddr *peer, int *len, int flags) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::connect (const struct sockaddr *name, int namelen) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::getsockname (struct sockaddr *name, int *namelen) { struct sockaddr_un sun; sun.sun_family = AF_UNIX; 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_unix::getpeername (struct sockaddr *name, int *namelen) { struct sockaddr_un sun; memset (&sun, 0, sizeof sun); sun.sun_family = AF_UNIX; 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 0; } int fhandler_socket_unix::shutdown (int how) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::close () { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::getpeereid (pid_t *pid, uid_t *euid, gid_t *egid) { if (get_socket_type () != SOCK_STREAM) { set_errno (EINVAL); 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; } ssize_t fhandler_socket_unix::recvfrom (void *ptr, size_t len, int flags, struct sockaddr *from, int *fromlen) { set_errno (EAFNOSUPPORT); return -1; } ssize_t fhandler_socket_unix::recvmsg (struct msghdr *msg, int flags) { set_errno (EAFNOSUPPORT); return -1; } void __reg3 fhandler_socket_unix::read (void *ptr, size_t& len) { set_errno (EAFNOSUPPORT); len = 0; } ssize_t __stdcall fhandler_socket_unix::readv (const struct iovec *, int iovcnt, ssize_t tot) { set_errno (EAFNOSUPPORT); return -1; } ssize_t fhandler_socket_unix::sendto (const void *in_ptr, size_t len, int flags, const struct sockaddr *to, int tolen) { set_errno (EAFNOSUPPORT); return -1; } ssize_t fhandler_socket_unix::sendmsg (const struct msghdr *msg, int flags) { set_errno (EAFNOSUPPORT); return -1; } ssize_t __stdcall fhandler_socket_unix::write (const void *ptr, size_t len) { set_errno (EAFNOSUPPORT); return -1; } ssize_t __stdcall fhandler_socket_unix::writev (const struct iovec *, int iovcnt, ssize_t tot) { set_errno (EAFNOSUPPORT); return -1; } int fhandler_socket_unix::setsockopt (int level, int optname, const void *optval, socklen_t optlen) { /* Preprocessing setsockopt. */ switch (level) { case SOL_SOCKET: switch (optname) { case SO_PASSCRED: break; case SO_REUSEADDR: saw_reuseaddr (*(int *) optval); break; case SO_RCVBUF: rmem (*(int *) optval); break; case SO_SNDBUF: wmem (*(int *) optval); break; case SO_RCVTIMEO: case SO_SNDTIMEO: if (optlen < (socklen_t) sizeof (struct timeval)) { set_errno (EINVAL); return -1; } if (!timeval_to_ms ((struct timeval *) optval, (optname == SO_RCVTIMEO) ? rcvtimeo () : sndtimeo ())) { set_errno (EDOM); return -1; } break; default: /* AF_UNIX sockets simply ignore all other SOL_SOCKET options. */ break; } break; default: set_errno (ENOPROTOOPT); return -1; } return 0; } int fhandler_socket_unix::getsockopt (int level, int optname, const void *optval, socklen_t *optlen) { /* Preprocessing getsockopt.*/ switch (level) { case SOL_SOCKET: switch (optname) { case SO_ERROR: { int *e = (int *) optval; *e = 0; break; } case SO_PASSCRED: break; case SO_PEERCRED: { struct ucred *cred = (struct ucred *) optval; if (*optlen < (socklen_t) sizeof *cred) { set_errno (EINVAL); return -1; } int 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; break; } case SO_RCVTIMEO: case SO_SNDTIMEO: { struct timeval *time_out = (struct timeval *) optval; if (*optlen < (socklen_t) sizeof *time_out) { set_errno (EINVAL); return -1; } 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; break; } case SO_TYPE: { unsigned int *type = (unsigned int *) optval; *type = get_socket_type (); *optlen = (socklen_t) sizeof *type; break; } /* AF_UNIX 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; break; } default: { unsigned int *val = (unsigned int *) optval; *val = 0; *optlen = (socklen_t) sizeof *val; break; } } break; default: set_errno (ENOPROTOOPT); return -1; } return 0; } int fhandler_socket_unix::ioctl (unsigned int cmd, void *p) { int ret; switch (cmd) { case FIOASYNC: #ifdef __x86_64__ case _IOW('f', 125, int): #endif break; case FIONREAD: #ifdef __x86_64__ case _IOR('f', 127, int): #endif case FIONBIO: case SIOCATMARK: break; default: ret = fhandler_socket::ioctl (cmd, p); break; } return ret; } int fhandler_socket_unix::fcntl (int cmd, intptr_t arg) { int ret; switch (cmd) { case F_SETOWN: break; case F_GETOWN: break; default: ret = fhandler_socket::fcntl (cmd, arg); break; } return ret; } int __reg2 fhandler_socket_unix::fstat (struct stat *buf) { int ret; if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::fstat (buf); ret = fhandler_base::fstat_fs (buf); if (!ret) { buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFSOCK; buf->st_size = 0; } return ret; } int __reg2 fhandler_socket_unix::fstatvfs (struct statvfs *sfs) { if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::fstatvfs (sfs); fhandler_disk_file fh (pc); fh.get_device () = FH_FS; return fh.fstatvfs (sfs); } int fhandler_socket_unix::fchmod (mode_t newmode) { if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::fchmod (newmode); fhandler_disk_file fh (pc); fh.get_device () = FH_FS; return fh.fchmod (S_IFSOCK | adjust_socket_file_mode (newmode)); } int fhandler_socket_unix::fchown (uid_t uid, gid_t gid) { if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::fchown (uid, gid); fhandler_disk_file fh (pc); return fh.fchown (uid, gid); } int fhandler_socket_unix::facl (int cmd, int nentries, aclent_t *aclbufp) { if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::facl (cmd, nentries, aclbufp); fhandler_disk_file fh (pc); return fh.facl (cmd, nentries, aclbufp); } int fhandler_socket_unix::link (const char *newpath) { if (!get_sun_path () || get_sun_path ()[0] == '\0') return fhandler_socket::link (newpath); fhandler_disk_file fh (pc); return fh.link (newpath); }