Commit 55f877f0 authored by Rémi Denis-Courmont's avatar Rémi Denis-Courmont

Implement root wrapper to allow using privileged TCP ports

while not running the whole VLC as root - closes #440
parent a21fa225
...@@ -69,7 +69,7 @@ dist_pkginclude_HEADERS = \ ...@@ -69,7 +69,7 @@ dist_pkginclude_HEADERS = \
include/vlc/input.h \ include/vlc/input.h \
include/vlc/intf.h \ include/vlc/intf.h \
include/vlc/control.h \ include/vlc/control.h \
include/vlc/control_structures.h \ include/vlc/control_structures.h \
$(NULL) $(NULL)
noinst_HEADERS = $(HEADERS_include) noinst_HEADERS = $(HEADERS_include)
...@@ -446,6 +446,7 @@ SOURCES_libvlc_common = \ ...@@ -446,6 +446,7 @@ SOURCES_libvlc_common = \
src/network/tcp.c \ src/network/tcp.c \
src/network/udp.c \ src/network/udp.c \
src/network/httpd.c \ src/network/httpd.c \
src/network/rootwrap.c \
src/network/tls.c \ src/network/tls.c \
src/misc/charset.c \ src/misc/charset.c \
src/misc/md5.c \ src/misc/md5.c \
......
/*****************************************************************************
* rootwrap.c
*****************************************************************************
* Copyright © 2005 Rémi Denis-Courmont
* $Id$
*
* Author: Rémi Denis-Courmont <rem # videolan.org>
*
* 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 <config.h>
#endif
#ifdef HAVE_GETEUID
# define ENABLE_ROOTWRAP 1
#endif
#ifdef ENABLE_ROOTWRAP
#include <stdlib.h> /* exit() */
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/resource.h> /* getrlimit() */
#include <sys/wait.h>
#include <sys/un.h>
#include <pwd.h> /* getpwnam(), getpwuid() */
#include <grp.h> /* setgroups() */
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
/*#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 <stddef.h>
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 */
...@@ -50,6 +50,8 @@ static int SocksHandshakeTCP( vlc_object_t *, ...@@ -50,6 +50,8 @@ static int SocksHandshakeTCP( vlc_object_t *,
const char *psz_host, int i_port ); const char *psz_host, int i_port );
extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype, extern int net_Socket( vlc_object_t *p_this, int i_family, int i_socktype,
int i_protocol ); int i_protocol );
extern int rootwrap_bind (int family, int socktype, int protocol,
const struct sockaddr *addr, size_t alen);
/***************************************************************************** /*****************************************************************************
* __net_ConnectTCP: * __net_ConnectTCP:
...@@ -302,13 +304,30 @@ int *__net_ListenTCP( vlc_object_t *p_this, const char *psz_host, int i_port ) ...@@ -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) #if defined(WIN32) || defined(UNDER_CE)
msg_Warn( p_this, "cannot bind socket (%i)", WSAGetLastError( ) ); msg_Warn( p_this, "cannot bind socket (%i)", WSAGetLastError( ) );
#else
msg_Warn( p_this, "cannot bind socket (%s)", strerror( errno ) );
#endif
net_Close( fd ); net_Close( fd );
continue; 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 */ /* Listen */
if( listen( fd, 100 ) == -1 ) if( listen( fd, 100 ) == -1 )
{ {
......
...@@ -45,6 +45,8 @@ ...@@ -45,6 +45,8 @@
static void SigHandler ( int i_signal ); static void SigHandler ( int i_signal );
#endif #endif
extern void rootwrap( void );
/***************************************************************************** /*****************************************************************************
* main: parse command line, start interface and spawn threads. * main: parse command line, start interface and spawn threads.
*****************************************************************************/ *****************************************************************************/
...@@ -73,6 +75,8 @@ int main( int i_argc, char *ppsz_argv[] ) ...@@ -73,6 +75,8 @@ int main( int i_argc, char *ppsz_argv[] )
} }
#endif #endif
rootwrap ();
/* Create a libvlc structure */ /* Create a libvlc structure */
i_ret = VLC_Create(); i_ret = VLC_Create();
if( i_ret < 0 ) if( i_ret < 0 )
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment