]> git.sesse.net Git - vlc/blob - src/network/rootwrap.c
Disable root wrapper on BeOS
[vlc] / src / network / rootwrap.c
1 /*****************************************************************************
2  * rootwrap.c
3  *****************************************************************************
4  * Copyright © 2005 Rémi Denis-Courmont
5  * $Id$
6  *
7  * Author: Rémi Denis-Courmont <rem # videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
22  *****************************************************************************/
23
24 #if HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #if defined (HAVE_GETEUID) && !defined (SYS_BEOS)
29 # define ENABLE_ROOTWRAP 1
30 #endif
31
32 #ifdef ENABLE_ROOTWRAP
33
34 #include <stdlib.h> /* exit() */
35 #include <stdio.h>
36 #include <string.h>
37
38 #include <sys/types.h>
39 #include <unistd.h>
40 #include <fcntl.h>
41 #include <sys/socket.h>
42 #ifdef HAVE_SYS_TIME_H
43 #include <sys/time.h>
44 #endif
45 #include <sys/uio.h>
46 #include <sys/resource.h> /* getrlimit() */
47 #include <sys/wait.h>
48 #include <sys/un.h>
49 #include <pwd.h> /* getpwnam(), getpwuid() */
50 #include <grp.h> /* setgroups() */
51 #include <errno.h>
52 #include <netinet/in.h>
53 #include <pthread.h>
54
55 /*#ifndef HAVE_CLEARENV
56 extern char **environ;
57
58 static int clearenv (void)
59 {
60     environ = NULL;
61     return 0;
62 }
63 #endif*/
64
65 /**
66  * Tries to find a real non-root user to use
67  */
68 static struct passwd *guess_user (void)
69 {
70     const char *name;
71     struct passwd *pw;
72     uid_t uid;
73
74     /* Try real UID */
75     uid = getuid ();
76     if (uid)
77         if ((pw = getpwuid (uid)) != NULL)
78             return pw;
79
80     /* Try sudo */
81     name = getenv ("SUDO_USER");
82     if (name != NULL)
83         if ((pw = getpwnam (name)) != NULL)
84             return pw;
85
86     /* Try VLC_USER */
87     name = getenv ("VLC_USER");
88     if (name != NULL)
89         if ((pw = getpwnam (name)) != NULL)
90             return pw;
91
92     /* Try vlc */
93     if ((pw = getpwnam ("vlc")) != NULL)
94         return pw;
95
96     return getpwuid (0);
97 }
98
99
100 static int is_allowed_port (uint16_t port)
101 {
102     port = ntohs (port);
103
104     return (port == 80) || (port == 443) || (port == 554);
105 }
106
107
108 static int send_err (int fd, int err)
109 {
110     return send (fd, &err, sizeof (err), 0) == sizeof (err) ? 0 : -1;
111 }
112
113 /**
114  * Ugly POSIX(?) code to pass a file descriptor to another process
115  */
116 static int send_fd (int p, int fd)
117 {
118     struct msghdr hdr;
119     struct iovec iov;
120     struct cmsghdr *cmsg;
121     char buf[CMSG_SPACE (sizeof (fd))];
122     int val = 0;
123
124     hdr.msg_name = NULL;
125     hdr.msg_namelen = 0;
126     hdr.msg_iov = &iov;
127     hdr.msg_iovlen = 1;
128     hdr.msg_control = buf;
129     hdr.msg_controllen = sizeof (buf);
130
131     iov.iov_base = &val;
132     iov.iov_len = sizeof (val);
133
134     cmsg = CMSG_FIRSTHDR (&hdr);
135     cmsg->cmsg_level = SOL_SOCKET;
136     cmsg->cmsg_type = SCM_RIGHTS;
137     cmsg->cmsg_len = CMSG_LEN (sizeof (fd));
138     memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd));
139     hdr.msg_controllen = cmsg->cmsg_len;
140
141     return sendmsg (p, &hdr, 0) == sizeof (val) ? 0 : -1;
142 }
143
144
145 /**
146  * Background process run as root to open privileged TCP ports.
147  */
148 static void rootprocess (int fd)
149 {
150     struct sockaddr_storage ss;
151
152     /* TODO:
153      *  - use libcap if available,
154      *  - call chroot
155      */
156     while (recv (fd, &ss, sizeof (ss), 0) == sizeof (ss))
157     {
158         unsigned len;
159         int sock;
160
161         switch (ss.ss_family)
162         {
163             case AF_INET:
164                 if (!is_allowed_port (((struct sockaddr_in *)&ss)->sin_port))
165                 {
166                     if (send_err (fd, EACCES))
167                         return;
168                     continue;
169                 }
170                 len = sizeof (struct sockaddr_in);
171                 break;
172
173 #ifdef AF_INET6
174             case AF_INET6:
175                 if (!is_allowed_port (((struct sockaddr_in6 *)&ss)->sin6_port))
176                 {
177                     if (send_err (fd, EACCES))
178                         return;
179                     continue;
180                 }
181                 len = sizeof (struct sockaddr_in6);
182                 break;
183 #endif
184
185             default:
186                 if (send_err (fd, EAFNOSUPPORT))
187                     return;
188                 continue;
189         }
190
191         sock = socket (ss.ss_family, SOCK_STREAM, IPPROTO_TCP);
192         if (sock != -1)
193         {
194             const int val = 1;
195
196             setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val));
197             if (ss.ss_family == AF_INET6)
198                 setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof (val));
199
200             if (bind (sock, (struct sockaddr *)&ss, len) == 0)
201             {
202                 send_fd (fd, sock);
203                 close (sock);
204                 continue;
205             }
206         }
207         send_err (fd, errno);
208     }
209 }
210
211 static int rootwrap_sock = -1;
212 static pid_t rootwrap_pid = -1;
213
214 static void close_rootwrap (void)
215 {
216     close (rootwrap_sock);
217     waitpid (rootwrap_pid, NULL, 0);
218 }
219
220 void rootwrap (void)
221 {
222     struct rlimit lim;
223     int fd, pair[2];
224     struct passwd *pw;
225     uid_t u;
226
227     u = geteuid ();
228     /* Are we running with root privileges? */
229     if (u != 0)
230     {
231         setuid (u);
232         return;
233     }
234
235     /* Make sure 0, 1 and 2 are opened, and only these. */
236     if (getrlimit (RLIMIT_NOFILE, &lim))
237         exit (1);
238
239     for (fd = 3; ((unsigned)fd) < lim.rlim_cur; fd++)
240         close (fd);
241
242     fd = dup (2);
243     if (fd <= 2)
244         exit (1);
245     close (fd);
246
247     fputs ("Starting VLC root wrapper...", stderr);
248
249     pw = guess_user ();
250     if (pw == NULL)
251         return; /* Should we rather print an error and exit ? */
252
253     u = pw->pw_uid,
254     fprintf (stderr, " using UID %u (%s)\n", (unsigned)u, pw->pw_name);
255     if (u == 0)
256     {
257         fputs ("***************************************\n"
258                "* Running VLC as root is discouraged. *\n"
259                "***************************************\n"
260                "\n"
261                " It is potentially dangerous, "
262                 "and might not even work properly.\n", stderr);
263         return;
264     }
265
266     /* GID */
267     initgroups (pw->pw_name, pw->pw_gid);
268     setgid (pw->pw_gid);
269
270     if (socketpair (AF_LOCAL, SOCK_STREAM, 0, pair))
271     {
272         perror ("socketpair");
273         goto nofork;
274     }
275
276     switch (rootwrap_pid = fork ())
277     {
278         case -1:
279             perror ("fork");
280             close (pair[0]);
281             close (pair[1]);
282             break;
283
284         case 0:
285             close (0);
286             close (1);
287             close (2);
288             close (pair[0]);
289             rootprocess (pair[1]);
290             exit (0);
291
292         default:
293             close (pair[1]);
294             rootwrap_sock = pair[0];
295             break;
296     }
297
298 nofork:
299     /* UID */
300     setuid (u);
301
302     atexit (close_rootwrap);
303 }
304
305
306 /**
307  * Ugly POSIX(?) code to receive a file descriptor from another process
308  */
309 static int recv_fd (int p)
310 {
311     struct msghdr hdr;
312     struct iovec iov;
313     struct cmsghdr *cmsg;
314     int val, fd;
315     char buf[CMSG_SPACE (sizeof (fd))];
316
317     hdr.msg_name = NULL;
318     hdr.msg_namelen = 0;
319     hdr.msg_iov = &iov;
320     hdr.msg_iovlen = 1;
321     hdr.msg_control = buf;
322     hdr.msg_controllen = sizeof (buf);
323
324     iov.iov_base = &val;
325     iov.iov_len = sizeof (val);
326
327     if (recvmsg (p, &hdr, 0) != sizeof (val))
328         return -1;
329
330     for (cmsg = CMSG_FIRSTHDR (&hdr); cmsg != NULL;
331          cmsg = CMSG_NXTHDR (&hdr, cmsg))
332     {
333         if ((cmsg->cmsg_level == SOL_SOCKET)
334          && (cmsg->cmsg_type = SCM_RIGHTS)
335          && (cmsg->cmsg_len >= CMSG_LEN (sizeof (fd))))
336         {
337             memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd));
338             return fd;
339         }
340     }
341
342     return -1;
343 }
344
345 /**
346  * Tries to obtain a bound TCP socket from the root process
347  */
348 int rootwrap_bind (int family, int socktype, int protocol,
349                    const struct sockaddr *addr, size_t alen)
350 {
351     /* can't use libvlc */
352     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
353
354     struct sockaddr_storage ss;
355     int fd;
356
357     if (rootwrap_sock == -1)
358     {
359         errno = EACCES;
360         return -1;
361     }
362
363     switch (family)
364     {
365         case AF_INET:
366             if (alen < sizeof (struct sockaddr_in))
367             {
368                 errno = EINVAL;
369                 return -1;
370             }
371             break;
372
373 #ifdef AF_INET6
374         case AF_INET6:
375             if (alen < sizeof (struct sockaddr_in6))
376             {
377                 errno = EINVAL;
378                 return -1;
379             }
380             break;
381 #endif
382
383         default:
384             errno = EAFNOSUPPORT;
385             return -1;
386     }
387
388     if (family != addr->sa_family)
389     {
390         errno = EAFNOSUPPORT;
391         return -1;
392     }
393
394     /* Only TCP is implemented at the moment */
395     if ((socktype != SOCK_STREAM)
396      || (protocol && (protocol != IPPROTO_TCP)))
397     {
398         errno = EACCES;
399         return -1;
400     }
401
402     memset (&ss, 0, sizeof (ss));
403     memcpy (&ss, addr, alen > sizeof (ss) ? sizeof (ss) : alen);
404
405     pthread_mutex_lock (&mutex);
406     if (send (rootwrap_sock, &ss, sizeof (ss), 0) != sizeof (ss))
407         return -1;
408
409     fd = recv_fd (rootwrap_sock);
410     pthread_mutex_unlock (&mutex);
411
412     if (fd != -1)
413     {
414         int val;
415
416         val = fcntl (fd, F_GETFL, 0);
417         fcntl (fd, F_SETFL, ((val != -1) ? val : 0) | O_NONBLOCK);
418     }
419
420     return fd;
421 }
422
423 #else
424 # include <stddef.h>
425
426 struct sockaddr;
427
428 void rootwrap (void)
429 {
430 }
431
432 int rootwrap_bind (int family, int socktype, int protocol,
433                    const struct sockaddr *addr, size_t alen)
434 {
435     return -1;
436 }
437
438 #endif /* ENABLE_ROOTWRAP */