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