]> git.sesse.net Git - vlc/blob - src/network/rootwrap.c
* 2nd review of /src/* \ libvlc.h (refs #438)
[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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, 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 #ifdef AF_INET6
198             if (ss.ss_family == AF_INET6)
199                 setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof (val));
200 #endif
201             if (bind (sock, (struct sockaddr *)&ss, len) == 0)
202             {
203                 send_fd (fd, sock);
204                 close (sock);
205                 continue;
206             }
207         }
208         send_err (fd, errno);
209     }
210 }
211
212 static int rootwrap_sock = -1;
213 static pid_t rootwrap_pid = -1;
214
215 static void close_rootwrap (void)
216 {
217     close (rootwrap_sock);
218     waitpid (rootwrap_pid, NULL, 0);
219 }
220
221 void rootwrap (void)
222 {
223     struct rlimit lim;
224     int fd, pair[2];
225     struct passwd *pw;
226     uid_t u;
227
228     u = geteuid ();
229     /* Are we running with root privileges? */
230     if (u != 0)
231     {
232         setuid (u);
233         return;
234     }
235
236     /* Make sure 0, 1 and 2 are opened, and only these. */
237     if (getrlimit (RLIMIT_NOFILE, &lim))
238         exit (1);
239
240     for (fd = 3; ((unsigned)fd) < lim.rlim_cur; fd++)
241         close (fd);
242
243     fd = dup (2);
244     if (fd <= 2)
245         exit (1);
246     close (fd);
247
248     fputs ("starting VLC root wrapper...", stderr);
249
250     pw = guess_user ();
251     if (pw == NULL)
252         return; /* Should we rather print an error and exit ? */
253
254     u = pw->pw_uid,
255     fprintf (stderr, " using UID %u (%s)\n", (unsigned)u, pw->pw_name);
256     if (u == 0)
257     {
258         fputs ("***************************************\n"
259                "* Running VLC as root is discouraged. *\n"
260                "***************************************\n"
261                "\n"
262                " It is potentially dangerous, "
263                 "and might not even work properly.\n", stderr);
264         return;
265     }
266
267     /* GID */
268     initgroups (pw->pw_name, pw->pw_gid);
269     setgid (pw->pw_gid);
270
271     if (socketpair (AF_LOCAL, SOCK_STREAM, 0, pair))
272     {
273         perror ("socketpair");
274         goto nofork;
275     }
276
277     switch (rootwrap_pid = fork ())
278     {
279         case -1:
280             perror ("fork");
281             close (pair[0]);
282             close (pair[1]);
283             break;
284
285         case 0:
286             close (0);
287             close (1);
288             close (2);
289             close (pair[0]);
290             rootprocess (pair[1]);
291             exit (0);
292
293         default:
294             close (pair[1]);
295             rootwrap_sock = pair[0];
296             break;
297     }
298
299 nofork:
300     /* UID */
301     setuid (u);
302
303     atexit (close_rootwrap);
304 }
305
306
307 /**
308  * Ugly POSIX(?) code to receive a file descriptor from another process
309  */
310 static int recv_fd (int p)
311 {
312     struct msghdr hdr;
313     struct iovec iov;
314     struct cmsghdr *cmsg;
315     int val, fd;
316     char buf[CMSG_SPACE (sizeof (fd))];
317
318     hdr.msg_name = NULL;
319     hdr.msg_namelen = 0;
320     hdr.msg_iov = &iov;
321     hdr.msg_iovlen = 1;
322     hdr.msg_control = buf;
323     hdr.msg_controllen = sizeof (buf);
324
325     iov.iov_base = &val;
326     iov.iov_len = sizeof (val);
327
328     if (recvmsg (p, &hdr, 0) != sizeof (val))
329         return -1;
330
331     for (cmsg = CMSG_FIRSTHDR (&hdr); cmsg != NULL;
332          cmsg = CMSG_NXTHDR (&hdr, cmsg))
333     {
334         if ((cmsg->cmsg_level == SOL_SOCKET)
335          && (cmsg->cmsg_type = SCM_RIGHTS)
336          && (cmsg->cmsg_len >= CMSG_LEN (sizeof (fd))))
337         {
338             memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd));
339             return fd;
340         }
341     }
342
343     return -1;
344 }
345
346 /**
347  * Tries to obtain a bound TCP socket from the root process
348  */
349 int rootwrap_bind (int family, int socktype, int protocol,
350                    const struct sockaddr *addr, size_t alen)
351 {
352     /* can't use libvlc */
353     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
354
355     struct sockaddr_storage ss;
356     int fd;
357
358     if (rootwrap_sock == -1)
359     {
360         errno = EACCES;
361         return -1;
362     }
363
364     switch (family)
365     {
366         case AF_INET:
367             if (alen < sizeof (struct sockaddr_in))
368             {
369                 errno = EINVAL;
370                 return -1;
371             }
372             break;
373
374 #ifdef AF_INET6
375         case AF_INET6:
376             if (alen < sizeof (struct sockaddr_in6))
377             {
378                 errno = EINVAL;
379                 return -1;
380             }
381             break;
382 #endif
383
384         default:
385             errno = EAFNOSUPPORT;
386             return -1;
387     }
388
389     if (family != addr->sa_family)
390     {
391         errno = EAFNOSUPPORT;
392         return -1;
393     }
394
395     /* Only TCP is implemented at the moment */
396     if ((socktype != SOCK_STREAM)
397      || (protocol && (protocol != IPPROTO_TCP)))
398     {
399         errno = EACCES;
400         return -1;
401     }
402
403     memset (&ss, 0, sizeof (ss));
404     memcpy (&ss, addr, alen > sizeof (ss) ? sizeof (ss) : alen);
405
406     pthread_mutex_lock (&mutex);
407     if (send (rootwrap_sock, &ss, sizeof (ss), 0) != sizeof (ss))
408         return -1;
409
410     fd = recv_fd (rootwrap_sock);
411     pthread_mutex_unlock (&mutex);
412
413     if (fd != -1)
414     {
415         int val;
416
417         val = fcntl (fd, F_GETFL, 0);
418         fcntl (fd, F_SETFL, ((val != -1) ? val : 0) | O_NONBLOCK);
419     }
420
421     return fd;
422 }
423
424 #else
425 # include <stddef.h>
426
427 struct sockaddr;
428
429 void rootwrap (void)
430 {
431 }
432
433 int rootwrap_bind (int family, int socktype, int protocol,
434                    const struct sockaddr *addr, size_t alen)
435 {
436     return -1;
437 }
438
439 #endif /* ENABLE_ROOTWRAP */