]> git.sesse.net Git - vlc/blob - src/network/rootwrap.c
Use (void) instead of VLC_UNUSED (with pierre agreement)
[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 #if defined (AF_INET6) && !defined (IPV6_V6ONLY)
56 # warning Uho, your IPv6 support is broken and has been disabled. Fix your C library.
57 # undef AF_INET6
58 #endif
59
60 #ifndef AF_LOCAL
61 # define AF_LOCAL AF_UNIX
62 #endif
63
64 /*#ifndef HAVE_CLEARENV
65 extern char **environ;
66
67 static int clearenv (void)
68 {
69     environ = NULL;
70     return 0;
71 }
72 #endif*/
73
74 /**
75  * Tries to find a real non-root user to use
76  */
77 static struct passwd *guess_user (void)
78 {
79     const char *name;
80     struct passwd *pw;
81     uid_t uid;
82
83     /* Try real UID */
84     uid = getuid ();
85     if (uid)
86         if ((pw = getpwuid (uid)) != NULL)
87             return pw;
88
89     /* Try sudo */
90     name = getenv ("SUDO_USER");
91     if (name != NULL)
92         if ((pw = getpwnam (name)) != NULL)
93             return pw;
94
95     /* Try VLC_USER */
96     name = getenv ("VLC_USER");
97     if (name != NULL)
98         if ((pw = getpwnam (name)) != NULL)
99             return pw;
100
101     /* Try vlc */
102     if ((pw = getpwnam ("vlc")) != NULL)
103         return pw;
104
105     return getpwuid (0);
106 }
107
108
109 static int is_allowed_port (uint16_t port)
110 {
111     port = ntohs (port);
112
113     return (port == 80) || (port == 443) || (port == 554);
114 }
115
116
117 static int send_err (int fd, int err)
118 {
119     return send (fd, &err, sizeof (err), 0) == sizeof (err) ? 0 : -1;
120 }
121
122 /**
123  * Ugly POSIX(?) code to pass a file descriptor to another process
124  */
125 static int send_fd (int p, int fd)
126 {
127     struct msghdr hdr;
128     struct iovec iov;
129     struct cmsghdr *cmsg;
130     char buf[CMSG_SPACE (sizeof (fd))];
131     int val = 0;
132
133     hdr.msg_name = NULL;
134     hdr.msg_namelen = 0;
135     hdr.msg_iov = &iov;
136     hdr.msg_iovlen = 1;
137     hdr.msg_control = buf;
138     hdr.msg_controllen = sizeof (buf);
139
140     iov.iov_base = &val;
141     iov.iov_len = sizeof (val);
142
143     cmsg = CMSG_FIRSTHDR (&hdr);
144     cmsg->cmsg_level = SOL_SOCKET;
145     cmsg->cmsg_type = SCM_RIGHTS;
146     cmsg->cmsg_len = CMSG_LEN (sizeof (fd));
147     memcpy (CMSG_DATA (cmsg), &fd, sizeof (fd));
148     hdr.msg_controllen = cmsg->cmsg_len;
149
150     return sendmsg (p, &hdr, 0) == sizeof (val) ? 0 : -1;
151 }
152
153
154 /**
155  * Background process run as root to open privileged TCP ports.
156  */
157 static void rootprocess (int fd)
158 {
159     struct sockaddr_storage ss;
160
161     /* TODO:
162      *  - use libcap if available,
163      *  - call chroot
164      */
165     while (recv (fd, &ss, sizeof (ss), 0) == sizeof (ss))
166     {
167         unsigned len;
168         int sock;
169
170         switch (ss.ss_family)
171         {
172             case AF_INET:
173                 if (!is_allowed_port (((struct sockaddr_in *)&ss)->sin_port))
174                 {
175                     if (send_err (fd, EACCES))
176                         return;
177                     continue;
178                 }
179                 len = sizeof (struct sockaddr_in);
180                 break;
181
182 #ifdef AF_INET6
183             case AF_INET6:
184                 if (!is_allowed_port (((struct sockaddr_in6 *)&ss)->sin6_port))
185                 {
186                     if (send_err (fd, EACCES))
187                         return;
188                     continue;
189                 }
190                 len = sizeof (struct sockaddr_in6);
191                 break;
192 #endif
193
194             default:
195                 if (send_err (fd, EAFNOSUPPORT))
196                     return;
197                 continue;
198         }
199
200         sock = socket (ss.ss_family, SOCK_STREAM, IPPROTO_TCP);
201         if (sock != -1)
202         {
203             const int val = 1;
204
205             setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof (val));
206 #ifdef AF_INET6
207             if (ss.ss_family == AF_INET6)
208                 setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof (val));
209 #endif
210             if (bind (sock, (struct sockaddr *)&ss, len) == 0)
211             {
212                 send_fd (fd, sock);
213                 close (sock);
214                 continue;
215             }
216         }
217         send_err (fd, errno);
218     }
219 }
220
221 static int rootwrap_sock = -1;
222 static pid_t rootwrap_pid = -1;
223
224 static void close_rootwrap (void)
225 {
226     close (rootwrap_sock);
227     waitpid (rootwrap_pid, NULL, 0);
228 }
229
230 void rootwrap (void)
231 {
232     struct rlimit lim;
233     int fd, pair[2];
234     struct passwd *pw;
235     uid_t u;
236
237     u = geteuid ();
238     /* Are we running with root privileges? */
239     if (u != 0)
240     {
241         setuid (u);
242         return;
243     }
244
245     /* Make sure 0, 1 and 2 are opened, and only these. */
246     if (getrlimit (RLIMIT_NOFILE, &lim))
247         exit (1);
248
249     for (fd = 3; ((unsigned)fd) < lim.rlim_cur; fd++)
250         close (fd);
251
252     fd = dup (2);
253     if (fd <= 2)
254         exit (1);
255     close (fd);
256
257     fputs ("starting VLC root wrapper...", stderr);
258
259     pw = guess_user ();
260     if (pw == NULL)
261         return; /* Should we rather print an error and exit ? */
262
263     u = pw->pw_uid,
264     fprintf (stderr, " using UID %u (%s)\n", (unsigned)u, pw->pw_name);
265     if (u == 0)
266     {
267         fputs ("***************************************\n"
268                "* Running VLC as root is discouraged. *\n"
269                "***************************************\n"
270                "\n"
271                " It is potentially dangerous, "
272                 "and might not even work properly.\n", stderr);
273         return;
274     }
275
276     /* GID */
277     initgroups (pw->pw_name, pw->pw_gid);
278     setgid (pw->pw_gid);
279
280     if (socketpair (AF_LOCAL, SOCK_STREAM, 0, pair))
281     {
282         perror ("socketpair");
283         goto nofork;
284     }
285
286     switch (rootwrap_pid = fork ())
287     {
288         case -1:
289             perror ("fork");
290             close (pair[0]);
291             close (pair[1]);
292             break;
293
294         case 0:
295             close (0);
296             close (1);
297             close (2);
298             close (pair[0]);
299             rootprocess (pair[1]);
300             exit (0);
301
302         default:
303             close (pair[1]);
304             rootwrap_sock = pair[0];
305             break;
306     }
307
308 nofork:
309     /* UID */
310     setuid (u);
311
312     atexit (close_rootwrap);
313 }
314
315
316 /**
317  * Ugly POSIX(?) code to receive a file descriptor from another process
318  */
319 static int recv_fd (int p)
320 {
321     struct msghdr hdr;
322     struct iovec iov;
323     struct cmsghdr *cmsg;
324     int val, fd;
325     char buf[CMSG_SPACE (sizeof (fd))];
326
327     hdr.msg_name = NULL;
328     hdr.msg_namelen = 0;
329     hdr.msg_iov = &iov;
330     hdr.msg_iovlen = 1;
331     hdr.msg_control = buf;
332     hdr.msg_controllen = sizeof (buf);
333
334     iov.iov_base = &val;
335     iov.iov_len = sizeof (val);
336
337     if (recvmsg (p, &hdr, 0) != sizeof (val))
338         return -1;
339
340     for (cmsg = CMSG_FIRSTHDR (&hdr); cmsg != NULL;
341          cmsg = CMSG_NXTHDR (&hdr, cmsg))
342     {
343         if ((cmsg->cmsg_level == SOL_SOCKET)
344          && (cmsg->cmsg_type = SCM_RIGHTS)
345          && (cmsg->cmsg_len >= CMSG_LEN (sizeof (fd))))
346         {
347             memcpy (&fd, CMSG_DATA (cmsg), sizeof (fd));
348             return fd;
349         }
350     }
351
352     return -1;
353 }
354
355 /**
356  * Tries to obtain a bound TCP socket from the root process
357  */
358 int rootwrap_bind (int family, int socktype, int protocol,
359                    const struct sockaddr *addr, size_t alen)
360 {
361     /* can't use libvlc */
362     static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
363
364     struct sockaddr_storage ss;
365     int fd;
366
367     if (rootwrap_sock == -1)
368     {
369         errno = EACCES;
370         return -1;
371     }
372
373     switch (family)
374     {
375         case AF_INET:
376             if (alen < sizeof (struct sockaddr_in))
377             {
378                 errno = EINVAL;
379                 return -1;
380             }
381             break;
382
383 #ifdef AF_INET6
384         case AF_INET6:
385             if (alen < sizeof (struct sockaddr_in6))
386             {
387                 errno = EINVAL;
388                 return -1;
389             }
390             break;
391 #endif
392
393         default:
394             errno = EAFNOSUPPORT;
395             return -1;
396     }
397
398     if (family != addr->sa_family)
399     {
400         errno = EAFNOSUPPORT;
401         return -1;
402     }
403
404     /* Only TCP is implemented at the moment */
405     if ((socktype != SOCK_STREAM)
406      || (protocol && (protocol != IPPROTO_TCP)))
407     {
408         errno = EACCES;
409         return -1;
410     }
411
412     memset (&ss, 0, sizeof (ss));
413     memcpy (&ss, addr, alen > sizeof (ss) ? sizeof (ss) : alen);
414
415     pthread_mutex_lock (&mutex);
416     if (send (rootwrap_sock, &ss, sizeof (ss), 0) != sizeof (ss))
417         return -1;
418
419     fd = recv_fd (rootwrap_sock);
420     pthread_mutex_unlock (&mutex);
421
422     if (fd != -1)
423     {
424         int val;
425
426         val = fcntl (fd, F_GETFL, 0);
427         fcntl (fd, F_SETFL, ((val != -1) ? val : 0) | O_NONBLOCK);
428     }
429
430     return fd;
431 }
432
433 #else
434 # include <stddef.h>
435
436 struct sockaddr;
437
438 void rootwrap (void)
439 {
440 }
441
442 int rootwrap_bind (int family, int socktype, int protocol,
443                    const struct sockaddr *addr, size_t alen)
444 {
445     (void)family;
446     (void)socktype;
447     (void)protocol;
448     (void)addr;
449     (void)alen;
450     return -1;
451 }
452
453 #endif /* ENABLE_ROOTWRAP */