diff --git a/Makefile.am b/Makefile.am index 34cb681590..bc807bb904 100644 --- a/Makefile.am +++ b/Makefile.am @@ -69,7 +69,7 @@ dist_pkginclude_HEADERS = \ include/vlc/input.h \ include/vlc/intf.h \ include/vlc/control.h \ - include/vlc/control_structures.h \ + include/vlc/control_structures.h \ $(NULL) noinst_HEADERS = $(HEADERS_include) @@ -446,6 +446,7 @@ SOURCES_libvlc_common = \ src/network/tcp.c \ src/network/udp.c \ src/network/httpd.c \ + src/network/rootwrap.c \ src/network/tls.c \ src/misc/charset.c \ src/misc/md5.c \ diff --git a/src/network/rootwrap.c b/src/network/rootwrap.c new file mode 100644 index 0000000000..28c1fd5198 --- /dev/null +++ b/src/network/rootwrap.c @@ -0,0 +1,461 @@ +/***************************************************************************** + * rootwrap.c + ***************************************************************************** + * Copyright © 2005 Rémi Denis-Courmont + * $Id$ + * + * Author: Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + *****************************************************************************/ + +#if HAVE_CONFIG_H +# include +#endif + +#ifdef HAVE_GETEUID +# define ENABLE_ROOTWRAP 1 +#endif + +#ifdef ENABLE_ROOTWRAP + +#include /* exit() */ +#include + +#include +#include +#include +#include +#include /* getrlimit() */ +#include +#include +#include /* getpwnam(), getpwuid() */ +#include /* setgroups() */ +#include +#include +#include + +/*#ifndef HAVE_CLEARENV +extern char **environ; + +static int clearenv (void) +{ + environ = NULL; + return 0; +} +#endif*/ + +/** + * Converts username to UID. + */ +static uid_t parse_user (const char *name) +{ + struct passwd *pw; + + pw = getpwnam (name); + if (pw == NULL) + return 0; + + return pw->pw_uid; +} + + +/** + * Tries to find a real non-root user ID + */ +static uid_t guess_user (void) +{ + const char *name; + uid_t uid; + + /* Try real UID */ + uid = getuid (); + if (uid) + return uid; + + /* Try sudo */ + name = getenv ("SUDO_USER"); + if (name != NULL) + { + uid = parse_user (name); + if (uid != 0) + return uid; + } + + /* Try VLC_USER */ + name = getenv ("VLC_USER"); + if (name != NULL) + { + uid = parse_user (name); + if (uid != 0) + return uid; + } + + /* Try vlc */ + uid = parse_user ("vlc"); + if (uid != 0) + return uid; + + /* Try nobody */ + uid = parse_user ("nobody"); + if (uid != 0) + return uid; + + return 65534; +} + + +/** + * Returns the main GID associated with a given UID. + */ +static gid_t guess_gid (uid_t uid) +{ + struct passwd *pw; + + pw = getpwuid (uid); + if (pw != NULL) + return pw->pw_gid; + return 65534; +} + + +static int is_allowed_port (uint16_t port) +{ + port = ntohs (port); + + return (port == 80) || (port == 443) || (port == 554); +} + + +static int send_err (int fd, int err) +{ + return send (fd, &err, sizeof (err), 0) == sizeof (err) ? 0 : -1; +} + +/** + * Ugly POSIX(?) code to pass a file descriptor to another process + */ +static int send_fd (int p, int fd) +{ + struct msghdr hdr; + struct iovec iov; + struct cmsghdr *cmsg; + char buf[CMSG_SPACE (sizeof (fd))]; + int val = 0; + + hdr.msg_name = NULL; + hdr.msg_namelen = 0; + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_control = buf; + hdr.msg_controllen = sizeof (buf); + + iov.iov_base = &val; + iov.iov_len = sizeof (val); + + cmsg = CMSG_FIRSTHDR (&hdr); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN (sizeof (fd)); + memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd)); + hdr.msg_controllen = cmsg->cmsg_len; + + return sendmsg (p, &hdr, 0) == sizeof (val) ? 0 : -1; +} + + +/** + * Background process run as root to open privileged TCP ports. + */ +static void rootprocess (int fd) +{ + struct sockaddr_storage ss; + + /* TODO: + * - use libcap if available, + * - call chroot + */ + while (recv (fd, &ss, sizeof (ss), 0) == sizeof (ss)) + { + unsigned len; + int sock; + + switch (ss.ss_family) + { + case AF_INET: + if (!is_allowed_port (((struct sockaddr_in *)&ss)->sin_port)) + { + if (send_err (fd, EACCES)) + return; + continue; + } + len = sizeof (struct sockaddr_in); + break; + +#ifdef AF_INET6 + case AF_INET6: + if (!is_allowed_port (((struct sockaddr_in6 *)&ss)->sin6_port)) + { + if (send_err (fd, EACCES)) + return; + continue; + } + len = sizeof (struct sockaddr_in6); + break; +#endif + + default: + if (send_err (fd, EAFNOSUPPORT)) + return; + continue; + } + + sock = socket (ss.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (sock != -1) + { + const int val = 1; + + setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val)); + if (ss.ss_family == AF_INET6) + setsockopt (sock, SOL_IPV6, IPV6_V6ONLY, &val, sizeof (val)); + + if (bind (sock, (struct sockaddr *)&ss, len) == 0) + { + send_fd (fd, sock); + close (sock); + continue; + } + } + send_err (fd, errno); + } +} + +static int rootwrap_sock = -1; +static pid_t rootwrap_pid = -1; + +static void close_rootwrap (void) +{ + close (rootwrap_sock); + waitpid (rootwrap_pid, NULL, 0); +} + +void rootwrap (void) +{ + struct rlimit lim; + int fd, pair[2]; + uid_t u; + gid_t g; + + u = geteuid (); + /* Are we running with root privileges? */ + if (u != 0) + { + setuid (u); + return; + } + + /* Make sure 0, 1 and 2 are opened, and only these. */ + if (getrlimit (RLIMIT_NOFILE, &lim)) + exit (1); + + for (fd = 3; ((unsigned)fd) < lim.rlim_cur; fd++) + close (fd); + + fd = dup (2); + if (fd <= 2) + exit (1); + close (fd); + + fputs ("Starting VLC root wrapper...", stderr); + + u = guess_user (); + fprintf (stderr, " using UID %u", (unsigned)u); + + g = guess_gid (u); + fprintf (stderr, ", using GID %u\n", (unsigned)g); + + /* GID */ + setgid (g); + setgroups (0, NULL); + + if (socketpair (AF_LOCAL, SOCK_STREAM, 0, pair)) + { + perror ("socketpair"); + goto nofork; + } + + switch (rootwrap_pid = fork ()) + { + case -1: + perror ("fork"); + close (pair[0]); + close (pair[1]); + break; + + case 0: + close (0); + close (1); + close (2); + close (pair[0]); + rootprocess (pair[1]); + exit (0); + + default: + close (pair[1]); + rootwrap_sock = pair[0]; + break; + } + +nofork: + /* UID */ + setuid (u); + + atexit (close_rootwrap); +} + + +/** + * Ugly POSIX(?) code to receive a file descriptor from another process + */ +static int recv_fd (int p) +{ + struct msghdr hdr; + struct iovec iov; + struct cmsghdr *cmsg; + int val, fd; + char buf[CMSG_SPACE (sizeof (fd))]; + + hdr.msg_name = NULL; + hdr.msg_namelen = 0; + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + hdr.msg_control = buf; + hdr.msg_controllen = sizeof (buf); + + iov.iov_base = &val; + iov.iov_len = sizeof (val); + + if (recvmsg (p, &hdr, 0) != sizeof (val)) + return -1; + + for (cmsg = CMSG_FIRSTHDR (&hdr); cmsg != NULL; + cmsg = CMSG_NXTHDR (&hdr, cmsg)) + { + if ((cmsg->cmsg_level == SOL_SOCKET) + && (cmsg->cmsg_type = SCM_RIGHTS) + && (cmsg->cmsg_len >= CMSG_LEN (sizeof (fd)))) + { + memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd)); + return fd; + } + } + + return -1; +} + +/** + * Tries to obtain a bound TCP socket from the root process + */ +int rootwrap_bind (int family, int socktype, int protocol, + const struct sockaddr *addr, size_t alen) +{ + /* can't use libvlc */ + static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; + + struct sockaddr_storage ss; + int fd; + + if (rootwrap_sock == -1) + { + errno = EACCES; + return -1; + } + + switch (family) + { + case AF_INET: + if (alen < sizeof (struct sockaddr_in)) + { + errno = EINVAL; + return -1; + } + break; + +#ifdef AF_INET6 + case AF_INET6: + if (alen < sizeof (struct sockaddr_in6)) + { + errno = EINVAL; + return -1; + } + break; +#endif + + default: + errno = EAFNOSUPPORT; + return -1; + } + + if (family != addr->sa_family) + { + errno = EAFNOSUPPORT; + return -1; + } + + /* Only TCP is implemented at the moment */ + if ((socktype != SOCK_STREAM) + || (protocol && (protocol != IPPROTO_TCP))) + { + errno = EACCES; + return -1; + } + + memset (&ss, 0, sizeof (ss)); + memcpy (&ss, addr, alen > sizeof (ss) ? sizeof (ss) : alen); + + pthread_mutex_lock (&mutex); + if (send (rootwrap_sock, &ss, sizeof (ss), 0) != sizeof (ss)) + return -1; + + fd = recv_fd (rootwrap_sock); + pthread_mutex_unlock (&mutex); + + if (fd != -1) + { + int val; + + val = fcntl (fd, F_GETFL, 0); + fcntl (fd, F_SETFL, ((val != -1) ? val : 0) | O_NONBLOCK); + } + + return fd; +} + +#else +# include + +struct sockaddr; + +void rootwrap (void) +{ +} + +int rootwrap_bind (int family, int socktype, int protocol, + const struct sockaddr *addr, size_t alen) +{ + return -1; +} + +#endif /* ENABLE_ROOTWRAP */ diff --git a/src/network/tcp.c b/src/network/tcp.c index a05db09b8b..1c34418f47 100644 --- a/src/network/tcp.c +++ b/src/network/tcp.c @@ -50,6 +50,8 @@ static int SocksHandshakeTCP( vlc_object_t *, const char *psz_host, int i_port ); extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype, int i_protocol ); +extern int rootwrap_bind (int family, int socktype, int protocol, + const struct sockaddr *addr, size_t alen); /***************************************************************************** * __net_ConnectTCP: @@ -302,13 +304,30 @@ int *__net_ListenTCP( vlc_object_t *p_this, const char *psz_host, int i_port ) { #if defined(WIN32) || defined(UNDER_CE) msg_Warn( p_this, "cannot bind socket (%i)", WSAGetLastError( ) ); -#else - msg_Warn( p_this, "cannot bind socket (%s)", strerror( errno ) ); -#endif net_Close( fd ); continue; +#else + int saved_errno; + + saved_errno = errno; + net_Close( fd ); + fd = rootwrap_bind( ptr->ai_family, ptr->ai_socktype, + ptr->ai_protocol, ptr->ai_addr, + ptr->ai_addrlen ); + if( fd != -1 ) + { + msg_Dbg( p_this, "got socket %d from rootwrap", fd ); + } + else + { + msg_Warn( p_this, "cannot bind socket (%s)", + strerror( saved_errno ) ); + continue; + } +#endif } + msg_Dbg( p_this, "using socket %d from rootwrap", fd ); /* Listen */ if( listen( fd, 100 ) == -1 ) { diff --git a/src/vlc.c b/src/vlc.c index 67e9a19b5c..a039d71bb4 100644 --- a/src/vlc.c +++ b/src/vlc.c @@ -45,6 +45,8 @@ static void SigHandler ( int i_signal ); #endif +extern void rootwrap( void ); + /***************************************************************************** * main: parse command line, start interface and spawn threads. *****************************************************************************/ @@ -73,6 +75,8 @@ int main( int i_argc, char *ppsz_argv[] ) } #endif + rootwrap (); + /* Create a libvlc structure */ i_ret = VLC_Create(); if( i_ret < 0 )