Delete only 'sandbox-server-'-prefixed entries in '/var/lib/captive'.
[captive.git] / src / client / sandbox-server / main.c
1 /* $Id$
2  * filesystem sandbox server stub for libcaptive
3  * Copyright (C) 2003 Jan Kratochvil <project-captive@jankratochvil.net>
4  * 
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; exactly version 2 of June 1991 is required
8  * 
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  * 
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19
20 #include "config.h"
21
22 #include <glib/gmessages.h>
23 #include <stdlib.h>
24 #include <glib/giochannel.h>
25 #include <glib/gerror.h>
26 #include <popt.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <locale.h>
30 #include "captive/options.h"
31 #include <glib-object.h>
32 #include "captive/macros.h"
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36 #include <dirent.h>
37 #include <errno.h>
38 #include "../../libcaptive/sandbox/split.h"     /* for captive_sandbox_fd_closeup(); FIXME */
39 #include <grp.h>
40 #include <pwd.h>
41 #include <fcntl.h>
42 #include <sys/file.h>
43
44
45 /* CONFIG: */
46
47 #define CHROOT_PATH_HASHKEY_LENGTH (64)
48
49
50 GQuark sandbox_server_main_error_quark(void)
51 {
52 GQuark r=0;
53
54         if (!r)
55                 r=g_quark_from_static_string("sandbox-server");
56
57         return r;
58 }
59
60
61 static gchar *optarg_setuid=CAPTIVE_SANDBOX_SETUID;
62 static gchar *optarg_setgid=CAPTIVE_SANDBOX_SETGID;
63 static gchar *optarg_chroot=CAPTIVE_SANDBOX_CHROOT;
64
65 static const struct poptOption popt_table[]={
66 #define SANDBOX_SERVER_POPT(longname,argInfoP,argP,descripP,argDescripP) \
67                 { \
68                         longName: (longname), \
69                         shortName: 0, \
70                         argInfo: (argInfoP), \
71                         arg: (void *)argP, \
72                         val: 0, \
73                         descrip: (descripP), \
74                         argDescrip: (argDescripP), \
75                 }
76
77                 SANDBOX_SERVER_POPT("setuid"  ,POPT_ARG_STRING,&optarg_setuid,N_("Username or UID to become; \"-\" for disable"),N_("UID")),
78                 SANDBOX_SERVER_POPT("setgid"  ,POPT_ARG_STRING,&optarg_setgid,N_("Groupname or GID to become; \"-\" for disable"),N_("GID")),
79                 SANDBOX_SERVER_POPT("chroot"  ,POPT_ARG_STRING,&optarg_chroot,N_("Pathname to directory for chroot(2); \"-\" for disable"),N_("directory")),
80
81 #undef SANDBOX_SERVER_POPT
82                 POPT_AUTOHELP
83                 POPT_TABLEEND
84                 };
85
86
87 static gchar *fatal_argv0;
88
89 static void fatal(const char *fmt,...)
90 {
91 va_list ap;
92
93         fprintf(stderr,"%s: ",fatal_argv0);
94         va_start(ap,fmt);
95         vfprintf(stderr,fmt,ap);
96         va_end(ap);
97         fprintf(stderr,"!\nAborting!\n");
98         exit(EXIT_FAILURE);
99         /* NOTREACHED */
100         for (;;);
101 }
102
103 static void check_dir_safety(const gchar *dir)
104 {
105 gchar *local_dir;
106 const gchar *cs;
107 static gint depth=0;
108
109         if (++depth>=1000)
110                 fatal("Loop count >=%d during check_dir_safety(\"%s\")",depth,dir);
111
112         if (*dir!='/')
113                 fatal("chroot path \"%s\" not absolute",dir);
114         dir=captive_printf_alloca("%s/",dir);
115         local_dir=(gchar *)captive_strdup_alloca(dir);
116         for (cs=dir;cs;cs=strchr(cs+1,'/')) {
117 struct stat statbuf;
118
119                 g_assert(*cs=='/');
120                 /* Include the trailing '/' to resolve the root directory as "/". */
121                 memcpy(local_dir,dir,cs+1-dir);
122                 local_dir[cs+1-dir]=0;
123                 if (lstat(local_dir,&statbuf))
124                         fatal("lstat(\"%s\") of chroot path component: %m",local_dir);
125                 if (S_ISLNK(statbuf.st_mode)) {
126 char linkbuf[PATH_MAX];
127 int linkbuflen;
128
129                         if (0>(linkbuflen=readlink(local_dir,linkbuf,sizeof(linkbuf)-1)))
130                                 fatal("readlink(\"%s\") of chroot path component: %m",local_dir);
131                         linkbuf[linkbuflen]=0;
132                         check_dir_safety(linkbuf);
133                         if (stat(local_dir,&statbuf))   /* NOT lstat(2) */
134                                 fatal("stat(\"%s\") of chroot path component: %m",local_dir);
135                         }
136                 if (!S_ISDIR(statbuf.st_mode))
137                         fatal("lstat/stat(\"%s\") of chroot path component is !S_ISDIR",local_dir);
138                 if (statbuf.st_uid!=0)
139                         fatal("lstat/stat(\"%s\") of chroot path component has UID %d !=0",local_dir,(int)statbuf.st_uid);
140                 if (statbuf.st_gid!=0)
141                         fatal("lstat/stat(\"%s\") of chroot path component has GID %d !=0",local_dir,(int)statbuf.st_gid);
142                 if ((statbuf.st_mode&(S_IFDIR|S_ISVTX|0111)) != (S_IFDIR|0111))
143                         fatal("lstat/stat(\"%s\") of chroot path component has mode 0%o !=04[01]111",local_dir,(int)statbuf.st_mode);
144                 }
145
146         depth--;
147 }
148
149 static void chrooted_unlink_recursive(const gchar *pathname)
150 {
151 DIR *dir;
152 struct dirent *dirent;
153 static gint depth=0;
154 struct stat statbuf;
155
156         if (++depth>=1000)
157                 fatal("Loop count >=%d during chrooted_unlink_recursive(\"%s\")",depth,pathname);
158
159         /* Security: Do not allow anyone to escape the sandbox directory by symlinks. */
160         if (lstat(pathname,&statbuf))
161                 fatal("Cannot lstat(\"%s\") to delete leftover sandbox files: %m",pathname);
162         if (!S_ISDIR(statbuf.st_mode)) {
163                 if (unlink(pathname))
164                         fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
165                 goto done;
166                 }
167         if (!(dir=opendir(pathname)))
168                 fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
169         while (errno=0,(dirent=readdir(dir))) {
170 gchar *dirent_path;
171
172                 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
173                         continue;
174                 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
175                 chrooted_unlink_recursive(dirent_path);
176                 g_free(dirent_path);
177                 }
178         if (errno)
179                 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
180         if (closedir(dir))
181                 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
182         if (rmdir(pathname))
183                 fatal("Cannot rmdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
184
185 done:
186         depth--;
187 }
188
189 static void chrooted_cleanuplockeddirs(const gchar *pathname)
190 {
191 DIR *dir;
192 struct dirent *dirent;
193
194         if (!(dir=opendir(pathname))) {
195                 if (errno!=ENOTDIR)
196                         fatal("Cannot opendir(\"%s\") to delete leftover sandbox files: %m",pathname);
197                 /* errno==ENOTDIR, a regular file */
198                 if (unlink(pathname))
199                         fatal("Cannot unlink(\"%s\") to delete leftover sandbox files: %m",pathname);
200                 return;
201                 }
202         while (errno=0,(dirent=readdir(dir))) {
203 gchar *dirent_path;
204 int direntfd;
205
206                 if (!strcmp(dirent->d_name,".") || !strcmp(dirent->d_name,".."))
207                         continue;
208                 if (strncmp(dirent->d_name,"sandbox-server-",strlen("sandbox-server-")))
209                         continue;
210                 dirent_path=g_strdup_printf("%s/%s",pathname,dirent->d_name);
211                 if (-1==(direntfd=open(dirent_path,O_RDONLY))) {
212                         if (errno==ENOENT)      /* It could disappear in the meantime. */
213                                 goto next_dirent_free_dirent_path;
214                         fatal("Cannot open(\"%s\") as the child directory during delete of leftover sandbox files: %m",dirent_path);
215                         }
216                 if (flock(direntfd,LOCK_EX|LOCK_NB)) {
217                         if (errno==EWOULDBLOCK) /* Valid directory in use. */
218                                 goto next_dirent_close_direntfd;
219                         fatal("Cannot flock(\"%s\",LOCK_EX|LOCK_NB) child directory during delete of leftover sandbox files: %m",dirent_path);
220                         }
221                 chrooted_unlink_recursive(dirent_path);
222 next_dirent_close_direntfd:
223                 if (close(direntfd))
224                         fatal("Cannot close(\"%s\") child directory during delete of leftover sandbox files: %m",dirent_path);
225 next_dirent_free_dirent_path:
226                 g_free(dirent_path);
227                 }
228         if (errno)
229                 fatal("Cannot readdir(\"%s\") during delete of leftover sandbox files: %m",pathname);
230         if (closedir(dir))
231                 fatal("Cannot closedir(\"%s\") during delete of leftover sandbox files: %m",pathname);
232 }
233
234 static void chrooted_createdir(const gchar *dir,uid_t uid,gid_t gid,gboolean lock)
235 {
236 gint retries;
237
238         for (retries=0;retries<10;retries++) {
239 struct stat statbuf;
240 int dirfd;
241
242                 if (mkdir(dir,0711)) {
243                         if (errno!=EEXIST)
244                                 fatal("Failed to create chroot directory \"%s\": %m",dir);
245                         chrooted_unlink_recursive(dir);
246                         if (mkdir(dir,0711))
247                                 fatal("Failed to create chroot directory \"%s\" after attempted unlink: %m",dir);
248                         }
249                 if (!lock)
250                         break;
251                 dirfd=open(dir,O_RDONLY);
252                 if (dirfd==-1) {
253                         if (errno!=ENOENT)
254                                 fatal("Failed to open created chroot directory \"%s\" to lock it: %m",dir);
255                         continue;
256                         }
257                 /* Do not use 'LOCK_NB' here as the garbage collector should release it soon. */
258                 if (flock(dirfd,LOCK_EX))
259                         fatal("Failed to lock created chroot directory \"%s\": %m",dir);
260                 if (lstat(dir,&statbuf)) {
261                         if (errno!=ENOENT)
262                                 fatal("Failed to lstat(2) created chroot directory \"%s\": %m",dir);
263                         if (close(dirfd))
264                                 fatal("Failed to close created and locked chroot directory \"%s\": %m",dir);
265                         continue;
266                         }
267                 /* Leave 'dirfd' open to leave it LOCK_EX-ed. */
268                 break;
269                 }
270         if (chown(dir,uid,gid))
271                 fatal("Failed to chown(\"%s\",%d,%d): %m",dir,uid,gid);
272         if (chmod(dir,0711))    /* Just to be safe after chown(2); should be already done by mkdir(2). */
273                 fatal("Failed to chmod(\"%s\",0%o): %m",dir,0711);
274 }
275
276 static const gchar *chrooted_orbit_dir;
277
278 static void chroot_setup(gboolean fragile)
279 {
280 uid_t want_uid=0;
281 const gchar *want_uid_name=NULL;
282 gid_t want_gid=0;
283 char *endptr;
284
285         if (fragile) {
286                 captive_sandbox_fd_closeup(2 /* STDERR */ +1);
287                 clearenv();
288                 }
289
290 #define CLEANEMPTY(var) G_STMT_START { \
291                 if ((var) && (!*(var) || *(var)=='-')) \
292                         (var)=NULL; \
293                 } G_STMT_END
294         CLEANEMPTY(optarg_setgid);
295         CLEANEMPTY(optarg_setuid);
296         CLEANEMPTY(optarg_chroot);
297 #undef CLEANEMPTY
298
299         if (optarg_setgid) {
300 long want_gidl;
301
302                 want_gidl=strtol(optarg_setgid,&endptr,10);
303                 if (!endptr || !*endptr) {
304                         want_gid=want_gidl;
305                         if (want_gidl<=0 || want_gid!=(gid_t)want_gidl)
306                                 fatal("Numeric setgid not parsable: %s",optarg_setgid);
307                         }
308                 else {
309 struct group *want_gid_group=NULL;
310                         if (!(want_gid_group=getgrnam(optarg_setgid)))
311                                 fatal("Unable to query setgid group name \"%s\"",optarg_setgid);
312                         want_gid=want_gid_group->gr_gid;
313                         }
314                 }
315
316         if (optarg_setuid) {
317 long want_uidl;
318 struct passwd *want_uid_passwd;
319                 want_uidl=strtol(optarg_setuid,&endptr,10);
320                 if (!endptr || !*endptr) {
321                         want_uid=want_uidl;
322                         if (want_uidl<=0 || want_uid!=(gid_t)want_uidl)
323                                 fatal("Numeric setuid not parsable: %s",optarg_setuid);
324                         }
325                 else {
326                         if (!(want_uid_passwd=getpwnam(optarg_setuid)))
327                                 fatal("Unable to query setuid user name \"%s\"",optarg_setuid);
328                         want_uid=want_uid_passwd->pw_uid;
329                         }
330                 if (!want_uid)
331                         fatal("Unable to detect setuid UID");
332                 if (!(want_uid_passwd=getpwuid(want_uid)))
333                         fatal("Unable to query name of UID %d",(int)want_uid);
334                 want_uid_name=captive_strdup_alloca(want_uid_passwd->pw_name);
335                 }
336
337         if (fragile && !optarg_chroot)
338                 fatal("Fragile setuid/root environment but no --chroot set");
339         if (optarg_chroot) {
340 const gchar *chroot_pid_dir,*chroot_pid_hashkey_dir;
341 GRand *grand;
342 gchar chroot_hashkey[CHROOT_PATH_HASHKEY_LENGTH+1],*s;
343 gint gi;
344
345                 check_dir_safety(optarg_chroot);
346                 if (!(grand=g_rand_new()))      /* I hope g_rand_new() is security-safe. It looks so. */
347                         fatal("Cannot initialize random number generator g_rand_new()");
348                 for (s=chroot_hashkey;s<chroot_hashkey+CHROOT_PATH_HASHKEY_LENGTH;s++) {
349                         gi=g_rand_int_range(grand,0,10+26+26);
350                         /**/ if (gi>=0 && gi<10)
351                                 *s='0'+gi-(0);
352                         else if (gi>=10+0 && gi<10+26)
353                                 *s='a'+gi-(10);
354                         else if (gi>=10+26+0 && gi<10+26+26)
355                                 *s='A'+gi-(10+26);
356                         else g_assert_not_reached();
357                         }
358                 g_rand_free(grand);
359                 *s=0;
360                 if (geteuid()==0)       /* Not 'fragile' as we can be native 'root'. */
361                         chrooted_cleanuplockeddirs(optarg_chroot);
362                 chroot_pid_dir=captive_printf_alloca("%s/sandbox-server-%d",optarg_chroot,(int)getpid());
363                 chrooted_createdir(chroot_pid_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
364                                 TRUE);  /* lock */
365                 chroot_pid_hashkey_dir=captive_printf_alloca("%s/%s",chroot_pid_dir,chroot_hashkey);
366                 chrooted_createdir(chroot_pid_hashkey_dir,(!optarg_setuid ? (uid_t)-1 : want_uid),(!optarg_setgid ? (gid_t)-1 : want_gid),
367                                 FALSE); /* lock */
368                 if (chroot(chroot_pid_hashkey_dir))
369                         fatal("Failed to chroot(\"%s\"): %m",chroot_pid_hashkey_dir);
370                 if (chdir("/"))
371                         fatal("Failed to chdir(\"%s\"): %m","/");
372                 /* Now it is safe to set umask(0000) as we are protected by 'chroot_hashkey'.
373                  * We need it to permit our spawning parent to hardlink its sockets to us.
374                  */
375                 umask(0000);
376                 if (umask(0000)!=0000)
377                         fatal("Failed to set umask(0%o): %m",0000);
378                 if (want_uid_name) {
379                         printf("chroot_pid_hashkey_dir=%s\n",chroot_pid_hashkey_dir);
380                         chrooted_orbit_dir=g_strdup_printf("/tmp/orbit-%s",want_uid_name);
381                         printf("chrooted_orbit_dir=%s\n",chrooted_orbit_dir);
382                         }
383                 }
384
385         if (fragile && !optarg_setgid)
386                 fatal("Fragile setuid/root environment but no --setgid set");
387         if (optarg_setgid) {
388                 if (!want_gid || setgid(want_gid))
389                         fatal("Failed to setgid(%d)",(!want_gid ? -1 : (int)want_gid));
390                 if (setgroups(1 /* size */,&want_gid))
391                         fatal("Failed to setgroups(1,[%d])",(!want_gid ? -1 : (int)want_gid));
392                 }
393         if (fragile && !optarg_setuid)
394                 fatal("Fragile setuid/root environment but no --setuid set");
395         if (optarg_setuid) {
396                 if (!want_uid || setuid(want_uid))
397                         fatal("Failed to setuid(%d)",(!want_uid ? -1 : (int)want_uid));
398                 }
399
400         /* Prepare /tmp for /tmp/orbit-$username directories for ORBit2
401          * and also for parent's hardlink to its /tmp/captive-orbit-$pid directory. */
402         if (optarg_chroot) {
403                 if (mkdir("/tmp",S_ISVTX|0777))
404                         fatal("Failed to mkdir(\"%s\"): %m","/tmp");
405                 if (mkdir("/etc",0700))
406                         fatal("Failed to mkdir(\"%s\"): %m","/etc");
407                 if (want_uid_name && want_uid && want_gid) {
408 FILE *f;
409                         if (!(f=fopen("/etc/passwd","w")))
410                                 fatal("Failed to fopen(\"%s\",\"w\"): %m","/etc/passwd");
411                         if (0>fprintf(f,"%s:*:%d:%d:%s:%s:/bin/false",want_uid_name,(int)want_uid,(int)want_gid,want_uid_name,optarg_chroot))
412                                 fatal("Failed to fprintf(\"%s\"): %m","/etc/passwd");
413                         if (fclose(f))
414                                 fatal("Failed to fclose(\"%s\"): %m","/etc/passwd");
415                         }
416                 }
417
418         if (fragile) {
419 gid_t gid_list[2];
420 int gid_list_size,i;
421
422                 if (getuid()!=want_uid)
423                         fatal("getuid()=%d != want_uid=%d",(int)getuid(),(int)want_uid);
424                 if (geteuid()!=want_uid)
425                         fatal("geteuid()=%d != want_uid=%d",(int)geteuid(),(int)want_uid);
426                 if (getgid()!=want_gid)
427                         fatal("getgid()=%d != want_gid=%d",(int)getgid(),(int)want_gid);
428                 if (getegid()!=want_gid)
429                         fatal("getegid()=%d != want_gid=%d",(int)getegid(),(int)want_gid);
430                 gid_list_size=getgroups(G_N_ELEMENTS(gid_list),gid_list);
431                 for (i=0;i<gid_list_size;i++) {
432                         if (gid_list[i]!=want_gid)
433                                 fatal("getgroups() list member @%d %d != want_gid=%d",i,(int)gid_list[i],(int)want_gid);
434                         }
435                 }
436 }
437
438
439 int main(int argc,char **argv)
440 {
441 poptContext context;
442 int errint;
443 const char *cmd_arg;
444 struct captive_options options;
445 gboolean fragile;
446
447         g_log_set_always_fatal(~(0
448                         |G_LOG_LEVEL_MESSAGE
449                         |G_LOG_LEVEL_INFO
450                         |G_LOG_LEVEL_DEBUG
451                         ));
452
453         fatal_argv0=argv[0];
454         fragile=(getuid()!=geteuid() || getuid()==0 || geteuid()==0);
455
456 #ifndef MAINTAINER_MODE
457         if (fragile && (argc!=1 || argv[1]))
458                 fatal("Arguments invalid as running in fragile setuid/root environment");
459
460         if (fragile)
461                 chroot_setup(TRUE);
462 #endif /* MAINTAINER_MODE */
463
464         /* Initialize the i18n stuff */
465         setlocale(LC_ALL,"");
466         bindtextdomain(PACKAGE,LOCALEDIR);
467         textdomain(PACKAGE);
468
469         /* Initialize GObject subsystem of GLib. */
470         g_type_init();
471
472         captive_options_init(&options);
473         captive_options=&options;       /* for parsing by 'CAPTIVE_POPT_INCLUDE' */
474
475         context=poptGetContext(
476                         PACKAGE,        /* name */
477                         argc,(/*en-const*/const char **)argv,   /* argc,argv */
478                         popt_table,     /* options */
479                         POPT_CONTEXT_POSIXMEHARDER);    /* flags; && !POPT_CONTEXT_KEEP_FIRST */
480         if (context==NULL) {
481                 g_assert_not_reached(); /* argument recognization args_error */
482                 return EXIT_FAILURE;
483                 }
484         errint=poptReadDefaultConfig(context,
485                         TRUE);  /* useEnv */
486         if (errint!=0) {
487                 g_assert_not_reached(); /* argument recognization args_error */
488                 return EXIT_FAILURE;
489                 }
490         errint=poptGetNextOpt(context);
491         if (errint!=-1) {
492                 g_assert_not_reached(); /* some non-callbacked argument reached */
493                 return EXIT_FAILURE;
494                 }
495         cmd_arg=poptPeekArg(context);
496         if (cmd_arg) {
497                 g_assert_not_reached(); /* some non-option argument reached */
498                 return EXIT_FAILURE;
499                 }
500         /* 'cmd_arg'-style args gets cleared by 'poptFreeContext(context);' below */
501         poptFreeContext(context);
502
503 #ifdef MAINTAINER_MODE
504         chroot_setup(FALSE);
505 #endif /* MAINTAINER_MODE */
506
507         captive_options=NULL;   /* already parsed by 'CAPTIVE_POPT_INCLUDE' */
508
509         captive_corba_sandbox_child(chrooted_orbit_dir);
510
511         g_assert_not_reached();
512         return EXIT_SUCCESS;
513 }