added rpm_execcon() wrapper to make it work in SELinux environments
[util-vserver.git] / util-vserver / src / rpm-fake.c
1 // $Id$    --*- c++ -*--
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
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; version 2 of the License.
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 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "pathconfig.h"
24 #include "util.h"
25
26 #include <lib/vserver.h>
27 #include <lib/internal.h>
28 #include <lib_internal/sys_clone.h>
29
30 #include <sys/socket.h>
31 #include <dlfcn.h>
32 #include <stdlib.h>
33 #include <stdint.h>
34 #include <stdio.h>
35 #include <unistd.h>
36 #include <asm/unistd.h>
37 #include <string.h>
38 #include <stdbool.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <stdarg.h>
42 #include <sys/mount.h>
43 #include <linux/fs.h>
44 #include <sched.h>
45 #include <signal.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
48 #include <sys/un.h>
49 #include <fcntl.h>
50 #include <pwd.h>
51 #include <grp.h>
52
53
54   // from selinux.h
55   // FIXME: add configure autodetection and include <selinux.h> directly
56 int rpm_execcon(unsigned int verified,
57                 const char *filename,
58                 char *const argv[], char *const envp[]);
59
60
61 #define ENSC_WRAPPERS_PREFIX    "rpm-fake.so: "
62 #define ENSC_WRAPPERS_VSERVER   1
63 #define ENSC_WRAPPERS_UNISTD    1
64 #include <wrappers.h>
65
66 #ifndef CLONE_NEWNS
67 #  define CLONE_NEWNS   0x00020000
68 #endif
69
70 #define LIBNAME         "rpm-fake.so"
71 #define PLATFORM_FILE   "/etc/rpm/platform"
72
73 #define INIT(FILE,FUNC) FUNC##_func = ((__typeof__(FUNC) *) (xdlsym(FILE, #FUNC)))
74 #define DECLARE(FUNC)   static __typeof__(FUNC) *       FUNC##_func = 0
75
76
77 #define DBG_INIT        0x0001
78 #define DBG_VARIABLES   0x0002
79 #define DBG_RESOLVER    0x0004
80 #define DBG_EXECV       0x0008
81 #define DBG_VERBOSE0    0x8000
82 #define DBG_VERBOSE1    (0x4000 | DBG_VERBOSE0)
83 #define DBG_VERBOSE2    (0x2000 | DBG_VERBOSE1)
84
85 int                     wrapper_exit_code = 255;
86
87 static xid_t            ctx   = VC_NOCTX;
88 static uint32_t         caps  = ~0;
89 static int              flags = 0;
90 static char const *     mnts  = 0;
91 static char const *     root  = 0;
92 static int              pw_sock   = -1;
93 static int              sync_sock = -1;
94 static unsigned int     debug_level = 0;
95
96 static bool             is_initialized = false;
97
98   //DECLARE(rpm_execcon);
99   //DECLARE(execv);
100 DECLARE(getpwnam);
101 DECLARE(getgrnam);
102 DECLARE(endpwent);
103 DECLARE(endgrent);
104
105 static void             initRPMFake() __attribute__((__constructor__));
106 static void             exitRPMFake() __attribute__((__destructor__));
107
108 static inline bool
109 isDbgLevel(unsigned int level)
110 {
111   return ((debug_level&level)==level);
112 }
113
114 static void *
115 xdlsym(void *handle, const char *symbol)
116 {
117   void  *res = dlsym(handle, symbol);
118   if (res==0) {
119     char const  *error = dlerror();
120     write(2, symbol, strlen(symbol));
121     write(2, ": ", 2);
122     write(2, error, strlen(error));
123     write(2, "\n", 2);
124
125     _exit(255);
126   }
127
128   return res;
129 }
130
131 static void
132 showHelp()
133 {
134   WRITE_MSG(1,
135             "Usage: LD_PRELOAD=" LIBNAME " <executable> <args>*\n\n"
136             LIBNAME " unterstands the following environment variables:\n"
137             "  $RPM_FAKE_RESOLVER     ...  program which does the NSS resolving (defaults\n"
138             "                              to " RESOLVER_PROG ")\n"
139             "  $RPM_FAKE_RESOLVER_UID ...  uid of the resolver program\n"
140             "  $RPM_FAKE_RESOLVER_GID ...  gid of the resolver program\n"
141             "  $RPM_FAKE_CTX          ...  vserver context which shall be used for resolver\n"
142             "                              and scriptlets\n"
143             "  $RPM_FAKE_CAP          ...  linux capability remove-mask for the context\n"
144             "  $RPM_FAKE_FLAGS        ...  vserver flags of the context\n"
145             "  $RPM_FAKE_CHROOT       ...  directory of the chroot environment\n"
146             "  $RPM_FAKE_NAMESPACE_MOUNTS\n"
147             "                          ... colon separated list of directories which will\n"
148             "                              umounted before scriptlet execution\n\n"
149             "  $RPM_FAKE_HELP          ... shows this message\n"
150             "  $RPM_FAKE_VERSION       ... shows the version of this program\n\n"
151             "  $RPM_FAKE_DEBUG         ... sets the debuglevel bitmask\n\n"
152             "Please report bugs to " PACKAGE_BUGREPORT "\n");
153   exit(0);
154 }
155
156 static void
157 showVersion()
158 {
159   WRITE_MSG(1,
160             LIBNAME " " VERSION " -- wrapper around rpm\n"
161             "This program is part of " PACKAGE_STRING "\n\n"
162             "Copyright (C) 2003 Enrico Scholz\n"
163             VERSION_COPYRIGHT_DISCLAIMER);
164   exit(0);
165 }
166
167 static int
168 getAndClearEnv(char const *key, int dflt)
169 {
170   char          *env = getenv(key);
171   int           res;
172
173   if (env==0 || env[0]=='\0') res = dflt;
174   else {
175     res = atoi(env);
176     unsetenv(key);
177   }
178
179   return res;
180 }
181
182   /// \returns true iff we are in ctx after leaving this function
183 static bool
184 setupContext(xid_t xid, char const **xid_str)
185 {
186   bool          res = false;
187   
188   if (vc_isSupported(vcFEATURE_MIGRATE)) {
189     xid_t       rc=VC_NOCTX;
190
191     if ((xid==VC_DYNAMIC_XID || !vc_is_dynamic_xid(xid)) &&
192         (rc=vc_ctx_create(xid))==VC_NOCTX &&
193         errno!=EEXIST) {
194       perror(ENSC_WRAPPERS_PREFIX "vc_create_context()");
195       exit(255);
196     }
197
198     if (rc!=VC_NOCTX) {
199       char                      buf[sizeof(xid_t)*3 + 128];
200       size_t                    l;
201       struct vc_ctx_caps        caps;
202       
203       strcpy(buf, "rpm-fake.so #");
204       l = utilvserver_fmt_uint(buf+sizeof("rpm-fake.so #")-1, getppid());
205       Evc_set_vhi_name(rc, vcVHI_CONTEXT, buf, sizeof("rpm-fake.so #")+l-1);
206
207       caps.ccaps =  0ull;
208       caps.cmask = ~0ull;
209       caps.bcaps = ~vc_get_insecurebcaps();
210       caps.bmask = ~0ull;
211       Evc_set_ccaps(rc, &caps);
212       
213         // context will be activated later...
214
215       xid = rc;
216       res = true;
217     }
218   }
219
220   if (xid==VC_DYNAMIC_XID)
221     *xid_str = 0;
222   else {
223     char                buf[sizeof(xid_t)*3 + 2];
224     size_t              l;
225     
226     l        = utilvserver_fmt_uint(buf, xid); buf[l] = '\0';
227     *xid_str = strdup(buf);
228   }
229  
230   Ewrite(3, &xid, sizeof xid);
231   return res;
232 }
233
234 #if 0
235 static void
236 initPwSocket()
237 {
238   char const *  sock_name = getenv("RPM_FAKE_PWSOCKET");
239   if (sock_name!=0) {
240     int flag;
241     struct sockaddr_un  addr = {
242       .sun_family = AF_UNIX,
243     };
244
245     strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1);
246     addr.sun_path[sizeof(addr.sun_path)-1]='\0';
247     
248     if ((pw_sock=socket(AF_UNIX, SOCK_STREAM, 0))==-1 ||
249         connect(pw_sock, (struct sockaddr *)(&addr), sizeof addr)==-1 ||
250         (flag=fcntl(pw_sock, F_GETFD))==-1 ||
251         fcntl(pw_sock, F_SETFD, flag | FD_CLOEXEC)==-1) {
252       perror(ENSC_WRAPPERS_PREFIX "error while initializing pw-socket");
253       exit(255);
254     }
255   }
256   unsetenv("RPM_FAKE_PWSOCKET");
257 }
258 #else
259 static void
260 initPwSocket()
261 {
262   char const *  resolver = getenv("RPM_FAKE_RESOLVER");
263   if (resolver==0) resolver=RESOLVER_PROG;
264   
265   if (resolver!=0 && *resolver!='\0') {
266     int                 res_sock[2];
267     int                 sync_pipe[2];
268     pid_t               pid;
269     char const *        uid=0;
270     char const *        gid=0;
271
272     uid=getenv("RPM_FAKE_RESOLVER_UID");
273     gid=getenv("RPM_FAKE_RESOLVER_GID");
274
275     if (socketpair(AF_UNIX, SOCK_STREAM, 0, res_sock)==-1 ||
276         pipe(sync_pipe)==-1 ||
277         fcntl(res_sock[0],  F_SETFD, FD_CLOEXEC)==-1 ||
278         fcntl(sync_pipe[0], F_SETFD, FD_CLOEXEC)==-1) {
279       perror(ENSC_WRAPPERS_PREFIX "failed to create/initialize resolver-socket or pipe");
280       exit(255);
281     }
282
283     pid = fork();
284     if (pid==-1) {
285       perror(ENSC_WRAPPERS_PREFIX "fork()");
286       exit(255);
287     }
288
289     if (pid==0) {
290       char const        *args[20];
291       char const        **ptr  = args;
292       char const        *env[] = { "HOME=/", "PATH=/bin:/usr/bin", 0 };
293       char const        *xid_str;
294       char              flag_str[ sizeof(flags)*3 + 2];
295       char              caps_str[ sizeof(caps)*3  + 2];
296       
297       setsid();
298       dup2(res_sock[1],  0);
299       dup2(res_sock[1],  1);
300       if (sync_pipe[1]!=3) {
301         close(3);
302         dup2(sync_pipe[1], 3);
303         close(sync_pipe[1]);
304       }
305       close(res_sock[1]);
306         /* ... *socket[0] are marked as close-on-exec ...*/
307
308       flag_str[utilvserver_fmt_uint(flag_str, flags)] = '\0';
309       caps_str[utilvserver_fmt_uint(caps_str, caps)]  = '\0';
310
311       *ptr++ = resolver;
312       *ptr++ = "-F"; *ptr++ = flag_str;
313       *ptr++ = "-C"; *ptr++ = caps_str;
314       if (root)  { *ptr++ = "-r"; *ptr++ = root;  }
315       if (uid)   { *ptr++ = "-u"; *ptr++ = uid;   }
316       if (gid)   { *ptr++ = "-g"; *ptr++ = gid;   }
317
318       if (setupContext(ctx, &xid_str)) { *ptr++ = "-s"; }
319       else if (xid_str)                { *ptr++ = "-c"; *ptr++ = xid_str; }
320       
321       *ptr++ = 0;
322       execve(resolver, (char **)args, (char **)env);
323       perror(ENSC_WRAPPERS_PREFIX "failed to exec resolver");
324       exit(255);
325     }
326     else {
327       uint8_t           c;
328
329       close(res_sock[1]);
330       close(sync_pipe[1]);
331       pw_sock   = res_sock[0];
332       sync_sock = sync_pipe[0];
333
334       if (read(sync_sock, &ctx, sizeof ctx)!=sizeof(ctx) ||
335           read(sync_sock, &c, 1)!=1 ||
336           write(pw_sock, ".", 1)!=1 ||
337           read(pw_sock, &c,   1)!=1 ||
338           c!='.') {
339         WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "failed to initialize communication with resolver\n");
340         exit(255);
341       }
342
343       if (wait4(pid, 0, WNOHANG,0)==-1) {
344         WRITE_MSG(2, ENSC_WRAPPERS_PREFIX" unexpected initialization-error of resolver\n");
345         exit(255);
346       }
347     }
348
349     unsetenv("RPM_FAKE_RESOLVER_GID");
350     unsetenv("RPM_FAKE_RESOLVER_UID");
351     unsetenv("RPM_FAKE_RESOLVER");    
352   }
353 }
354 #endif
355
356 static void
357 initEnvironment()
358 {
359   int           syscall_rev;
360   int           syscall_nr;
361   
362   if (is_initialized) return;
363
364   syscall_rev = getAndClearEnv("RPM_FAKE_S_CONTEXT_REV", 0);
365   syscall_nr  = getAndClearEnv("RPM_FAKE_S_CONTEXT_NR",  273);
366   
367 #ifdef VC_ENABLE_API_LEGACY
368   {
369     extern void vc_init_internal_legacy(int ctx_rev, int ctx_number,
370                                         int ipv4_rev, int ipv4_number);
371   
372     vc_init_internal_legacy(syscall_rev, syscall_nr, 3, 274);
373   }
374 #endif
375
376   ctx       = getAndClearEnv("RPM_FAKE_CTX",  VC_DYNAMIC_XID);
377   caps      = getAndClearEnv("RPM_FAKE_CAP",  ~0x3404040f);
378   flags     = getAndClearEnv("RPM_FAKE_FLAGS", 0);
379   root      = getenv("RPM_FAKE_CHROOT");
380   mnts      = getenv("RPM_FAKE_NAMESPACE_MOUNTS");
381   if (mnts && *mnts) mnts = strdup(mnts);
382   else               mnts = 0;
383
384   unsetenv("RPM_FAKE_CHROOT");
385   unsetenv("RPM_FAKE_NAMESPACE_MOUNTS");
386
387   
388   is_initialized = true;
389 }
390
391 static void
392 initSymbols()
393 {
394     //INIT(RTLD_NEXT, rpm_execcon);
395     //INIT(RTLD_NEXT, execv);
396   INIT(RTLD_NEXT, getgrnam);
397   INIT(RTLD_NEXT, getpwnam);
398   INIT(RTLD_NEXT, endpwent);
399   INIT(RTLD_NEXT, endgrent);
400 }
401
402 static void
403 fixPreloadEnv()
404 {
405   char                  *env = getenv("LD_PRELOAD");
406   char                  *pos;
407
408     // the const <-> non-const assignment is not an issue since the following
409     // modifying operations will not be executed in the const-case
410   env = env ? env : "";
411   pos = strstr(env, LIBNAME);
412
413   if (pos!=0) {
414     char        *end_pos = pos + sizeof(LIBNAME);
415     bool        is_end = (end_pos[-1]=='\0');
416     char        *start_pos;
417
418     end_pos[-1] = '\0';
419     start_pos   = strrchr(env, ':');
420     if (start_pos==0) start_pos = env;
421     else if (!is_end) ++start_pos;
422
423     if (is_end) *start_pos = '\0';
424     else        memmove(start_pos, end_pos, strlen(end_pos)+1);
425   }
426
427 #ifdef DEBUG
428   if (isDbgLevel(DBG_VERBOSE1|DBG_VARIABLES)) {
429     WRITE_MSG(2, "env='");
430     WRITE_STR(2, env);
431     WRITE_MSG(2, "'\n");
432   }
433 #endif
434
435   if (*env=='\0') unsetenv("LD_PRELOAD");
436 }
437
438 void
439 initRPMFake()
440 {
441   if (getenv("RPM_FAKE_VERSION")) showVersion();
442   if (getenv("RPM_FAKE_HELP"))    showHelp();
443   
444   debug_level = getAndClearEnv("RPM_FAKE_DEBUG", 0);
445
446   if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> initRPMFake <<<<<\n");
447   
448   fixPreloadEnv();
449   initSymbols();
450   initEnvironment();
451   initPwSocket();
452
453 #if 0
454   if (isDbgLevel(DBG_VARIABLES|DBG_VERBOSE2)) {
455     
456   }
457 #endif
458 }
459
460 void
461 exitRPMFake()
462
463   if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> exitRPMFake <<<<<\n");
464   if (pw_sock!=-1) {
465     uint8_t     c;
466     read(sync_sock, &c, 1);
467     write(pw_sock, "Q", 1);
468   }
469 }
470
471
472   //============   the worker part   ===========
473
474
475 static bool
476 doPwStringRequest(uint32_t *result, char style, char const *name)
477 {
478   uint32_t      len = strlen(name);
479   uint8_t       code;
480   uint8_t       c;
481
482     // read the token...
483   read(sync_sock, &c, 1);
484
485   write(pw_sock, &style, 1);
486   write(pw_sock, &len,   sizeof len);
487   write(pw_sock, name,   len);
488   read (pw_sock, &code,  sizeof code);
489   read (pw_sock, result, sizeof *result);
490
491   return code!=0;
492 }
493
494 struct passwd *
495 getpwnam(const char * name)
496 {
497   if (pw_sock==-1) return getpwnam_func(name);
498   else {
499     static struct passwd        res = {
500       .pw_passwd = "*",
501       .pw_gid    = -1,
502       .pw_gecos  = "",
503       .pw_dir    = "/",
504       .pw_shell  = "/bin/false"
505     };
506
507     res.pw_name = (char *)(name);
508     if (!doPwStringRequest(&res.pw_uid, 'P', name)) return 0;
509     
510     return &res;
511   }
512 }
513
514 struct group *
515 getgrnam(const char * name)
516 {
517   if (pw_sock==-1) return getgrnam_func(name);
518   else {
519     static struct group         res = {
520       .gr_passwd = "*",
521       .gr_mem    = 0
522     };
523
524     res.gr_name = (char *)(name);
525     if (!doPwStringRequest(&res.gr_gid, 'G', name)) return 0;
526
527     return &res;
528   }
529 }
530
531 void
532 endgrent()
533 {
534   if (pw_sock==-1) endgrent_func();
535 }
536
537 void
538 endpwent()
539 {
540   if (pw_sock==-1) endpwent_func();
541 }
542
543
544 static int
545 execvWorker(char const *path, char * const argv[], char * const envp[])
546 {
547   int           res = -1;
548
549   if (vc_isSupported(vcFEATURE_MIGRATE))
550     res = vc_ctx_migrate(ctx);
551   else
552 #ifdef VC_ENABLE_API_COMPAT  
553     res = vc_new_s_context(ctx,caps,flags);
554 #else
555     WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "can not change context: migrate kernel feature missing and 'compat' API disabled\n");
556 #endif
557     
558   if (res!=-1)
559     res=execve(path, argv, envp);
560
561   return res;
562 }
563
564 struct ExecvParams
565 {
566     char const *        path;
567     char * const *      argv;
568     char * const *      envp;
569     char const *        mnts;
570 };
571
572 static int
573 removeNamespaceMountsChild(struct ExecvParams const *params)
574 {
575   char                  buf[strlen(params->mnts)+1], *ptr;
576
577   strcpy(buf, params->mnts);
578   ptr = strtok(buf, ":");
579   while (ptr) {
580     if (umount2(ptr, 0)==-1) {
581         // FIXME: What is the semantic for CLONE_NEWNS? Is it ok that mounts in
582         // chroots are visible only, when chroot is on /dev/root?
583         //
584         // For now, ignore any errors, but future versions should handle them.
585
586         //return -1;
587     }
588     ptr = strtok(0, ":");
589   }
590
591   return execvWorker(params->path, params->argv, params->envp);
592 }
593
594 static int
595 removeNamespaceMounts(char const *path,
596                       char * const argv[], char * const envp[])
597 {
598   if (mnts==0) return execvWorker(path, argv, envp);
599
600   {
601     int                         status;
602     pid_t                       p, pid;
603     struct ExecvParams          params;
604
605     params.path = path;
606     params.argv = argv;
607     params.envp = envp;
608     params.mnts = mnts;
609
610       // the rpmlib signal-handler is still active; use the default one to
611       // make wait4() working...
612     signal(SIGCHLD, SIG_DFL);
613
614 #ifdef NDEBUG
615     pid = sys_clone(CLONE_NEWNS|SIGCHLD|CLONE_VFORK, 0);
616 #else
617     pid = sys_clone(CLONE_NEWNS|SIGCHLD, 0);
618 #endif
619
620     switch (pid) {
621       case -1   :  return -1;
622       case 0    :  _exit(removeNamespaceMountsChild(&params));
623       default   :  break;
624     }
625         
626     while ((p=wait4(pid, &status, 0,0))==-1 &&
627            (errno==EINTR || errno==EAGAIN)) ;
628
629     if (p==-1)   return -1;
630
631     if (WIFEXITED(status))   _exit(WEXITSTATUS(status));
632     if (WIFSIGNALED(status)) kill(getpid(), WTERMSIG(status));
633
634     return -1;
635   }
636 }
637
638
639 int
640 execv(char const *path, char * const argv[])
641 {
642   extern char **environ;
643
644   return removeNamespaceMounts(path, argv, environ);
645 }
646
647 int
648 rpm_execcon(unsigned int UNUSED verified,
649             const char *filename,
650             char *const argv[], char *const envp[])
651 {
652   return removeNamespaceMounts(filename, argv, envp);
653 }