use --defaulttty options instead of setting tty manually
[util-vserver.git] / util-vserver / src / rpm-fake.c
index f063616..9160d39 100644 (file)
 #  include <config.h>
 #endif
 
+#include "pathconfig.h"
+#include "sys_clone.h"
+#include "util.h"
+
+#include "lib/vserver.h"
+#include "lib/internal.h"
+
+#include <sys/socket.h>
 #include <dlfcn.h>
 #include <stdlib.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <unistd.h>
+#include <asm/unistd.h>
 #include <string.h>
 #include <stdbool.h>
 #include <errno.h>
-#include <asm/unistd.h>
 #include <fcntl.h>
 #include <stdarg.h>
 #include <sys/mount.h>
 #include <signal.h>
 #include <sys/types.h>
 #include <sys/wait.h>
+#include <sys/un.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <grp.h>
+
+#define ENSC_WRAPPERS_PREFIX   "rpm-fake.so: "
+#define ENSC_WRAPPERS_VSERVER  1
+#define ENSC_WRAPPERS_UNISTD   1
+#include <wrappers.h>
+
+#ifndef CLONE_NEWNS
+#  define CLONE_NEWNS  0x00020000
+#endif
 
 #define LIBNAME                "rpm-fake.so"
 #define PLATFORM_FILE  "/etc/rpm/platform"
 
-#define INIT(FILE,FUNC)        FUNC##_func = ((__typeof__(FUNC) *) (dlsym(FILE, #FUNC)))
-#define DECLARE(FUNC)  static __typeof__(FUNC) *       FUNC##_func = 0
+#define INIT(FILE,FUNC)        FUNC##_func = ((__typeof__(FUNC) *) (xdlsym(FILE, #FUNC)))
+#define DECLARE(FUNC)  static __typeof__(FUNC) *       FUNC##_func = 0
 
-static bool            is_initialized = false;
-DECLARE(execv);
-  //DECLARE(open);
 
-static int def_NR_new_s_context = 259;
-#undef __NR_new_s_context
-static int __NR_new_s_context_rev0;
-static int __NR_new_s_context_rev1;
+#define DBG_INIT       0x0001
+#define DBG_VARIABLES  0x0002
+#define DBG_RESOLVER   0x0004
+#define DBG_EXECV      0x0008
+#define DBG_VERBOSE0   0x8000
+#define DBG_VERBOSE1   (0x4000 | DBG_VERBOSE0)
+#define DBG_VERBOSE2   (0x2000 | DBG_VERBOSE1)
 
-static int new_s_context_rev0(int, int, int);
-static int new_s_context_rev1(int, int *, int, int);
+int                    wrapper_exit_code = 255;
 
-_syscall3(int, new_s_context_rev0, int, newctx,              int, remove_cap, int, flags)
-_syscall4(int, new_s_context_rev1, int, nbctx,  int *, ctxs, int, remove_cap, int, flags)
+static xid_t           ctx   = VC_NOCTX;
+static uint32_t                caps  = ~0;
+static int             flags = 0;
+static char const *    mnts  = 0;
+static char const *    root  = 0;
+static int             pw_sock   = -1;
+static int             sync_sock = -1;
+static unsigned int    debug_level = 0;
 
-static int
-new_s_context(int rev,
-             int nbctx,  int *ctxs, int remove_cap, int flags)
-{
-  int          ret;
-  
-  switch (rev) {
-    case 0     :
-    {
-      int      ctx;
-      
-      switch (nbctx) {
-       case 0  :  ctx = -1;      break;
-       case 1  :  ctx = ctxs[0]; break;
-       default :  errno = EINVAL; return -1;
-      }
+static bool            is_initialized = false;
 
-      ret = new_s_context_rev0(ctx, remove_cap, flags);
+DECLARE(execv);
+DECLARE(getpwnam);
+DECLARE(getgrnam);
+DECLARE(endpwent);
+DECLARE(endgrent);
 
-      break;
-    }
+static void            initRPMFake() __attribute__((__constructor__));
+static void            exitRPMFake() __attribute__((__destructor__));
 
-    case 1     :
-      ret = new_s_context_rev1(nbctx, ctxs, remove_cap, flags);
-      break;
+static inline bool
+isDbgLevel(unsigned int level)
+{
+  return ((debug_level&level)==level);
+}
 
-    default    :
-      errno = EINVAL; return -1;
+static void *
+xdlsym(void *handle, const char *symbol)
+{
+  void *res = dlsym(handle, symbol);
+  if (res==0) {
+    char const *error = dlerror();
+    write(2, symbol, strlen(symbol));
+    write(2, ": ", 2);
+    write(2, error, strlen(error));
+    write(2, "\n", 2);
+
+    _exit(255);
   }
 
-  return ret;
+  return res;
 }
 
 static void
-initVserverSyscalls(int s_context_NR)
+showHelp()
 {
-  int  nr = (s_context_NR!=-1) ? s_context_NR : def_NR_new_s_context;
+  WRITE_MSG(1,
+           "Usage: LD_PRELOAD=" LIBNAME " <executable> <args>*\n\n"
+           LIBNAME " unterstands the following environment variables:\n"
+           "  $RPM_FAKE_RESOLVER     ...  program which does the NSS resolving (defaults\n"
+           "                              to " RESOLVER_PROG ")\n"
+           "  $RPM_FAKE_RESOLVER_UID ...  uid of the resolver program\n"
+           "  $RPM_FAKE_RESOLVER_GID ...  gid of the resolver program\n"
+           "  $RPM_FAKE_CTX          ...  vserver context which shall be used for resolver\n"
+           "                              and scriptlets\n"
+           "  $RPM_FAKE_CAP          ...  linux capability remove-mask for the context\n"
+           "  $RPM_FAKE_FLAGS        ...  vserver flags of the context\n"
+           "  $RPM_FAKE_CHROOT       ...  directory of the chroot environment\n"
+           "  $RPM_FAKE_NAMESPACE_MOUNTS\n"
+           "                          ... colon separated list of directories which will\n"
+            "                              umounted before scriptlet execution\n\n"
+           "  $RPM_FAKE_HELP          ... shows this message\n"
+           "  $RPM_FAKE_VERSION       ... shows the version of this program\n\n"
+           "  $RPM_FAKE_DEBUG         ... sets the debuglevel bitmask\n\n"
+           "Please report bugs to " PACKAGE_BUGREPORT "\n");
+  exit(0);
+}
 
-  __NR_new_s_context_rev0 = __NR_new_s_context_rev1 = nr;
+static void
+showVersion()
+{
+  WRITE_MSG(1,
+           LIBNAME " " VERSION " -- wrapper around rpm\n"
+           "This program is part of " PACKAGE_STRING "\n\n"
+           "Copyright (C) 2003 Enrico Scholz\n"
+           VERSION_COPYRIGHT_DISCLAIMER);
+  exit(0);
 }
 
 static int
 getAndClearEnv(char const *key, int dflt)
 {
-  char *env = getenv(key);
+  char         *env = getenv(key);
   int          res;
 
   if (env==0 || env[0]=='\0') res = dflt;
@@ -115,26 +170,226 @@ getAndClearEnv(char const *key, int dflt)
   return res;
 }
 
+  /// \returns true iff we are in ctx after leaving this function
+static bool
+setupContext(xid_t xid, char const **xid_str)
+{
+  bool         res = false;
+  
+  if (vc_isSupported(vcFEATURE_MIGRATE)) {
+    xid_t      rc=VC_NOCTX;
+
+    if ((xid==VC_DYNAMIC_XID || !vc_is_dynamic_xid(xid)) &&
+       (rc=vc_ctx_create(xid))==VC_NOCTX &&
+       errno!=EEXIST) {
+      perror(ENSC_WRAPPERS_PREFIX "vc_create_context()");
+      exit(255);
+    }
+
+    if (rc!=VC_NOCTX) {
+      char                     buf[sizeof(xid_t)*3 + 128];
+      size_t                   l;
+      struct vc_ctx_caps       caps;
+      
+      strcpy(buf, "rpm-fake.so #");
+      l = utilvserver_fmt_uint(buf+sizeof("rpm-fake.so #")-1, getppid());
+      Evc_set_vhi_name(rc, vcVHI_CONTEXT, buf, sizeof("rpm-fake.so #")+l-1);
+
+      caps.ccaps = ~0ull;
+      caps.cmask = ~0ull;
+      caps.bcaps = ~vc_get_insecurecaps();
+      caps.bmask = ~0ull;
+      Evc_set_ccaps(rc, &caps);
+      
+       // context will be activated later...
+
+      xid = rc;
+      res = true;
+    }
+  }
+
+  if (xid==VC_DYNAMIC_XID)
+    *xid_str = 0;
+  else {
+    char               buf[sizeof(xid_t)*3 + 2];
+    size_t             l;
+    
+    l        = utilvserver_fmt_uint(buf, xid); buf[l] = '\0';
+    *xid_str = strdup(buf);
+  }
+  Ewrite(3, &xid, sizeof xid);
+  return res;
+}
+
+#if 0
 static void
-initLib()
+initPwSocket()
 {
+  char const * sock_name = getenv("RPM_FAKE_PWSOCKET");
+  if (sock_name!=0) {
+    int flag;
+    struct sockaddr_un addr = {
+      .sun_family = AF_UNIX,
+    };
+
+    strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1);
+    addr.sun_path[sizeof(addr.sun_path)-1]='\0';
+    
+    if ((pw_sock=socket(AF_UNIX, SOCK_STREAM, 0))==-1 ||
+       connect(pw_sock, (struct sockaddr *)(&addr), sizeof addr)==-1 ||
+       (flag=fcntl(pw_sock, F_GETFD))==-1 ||
+       fcntl(pw_sock, F_SETFD, flag | FD_CLOEXEC)==-1) {
+      perror(ENSC_WRAPPERS_PREFIX "error while initializing pw-socket");
+      exit(255);
+    }
+  }
+  unsetenv("RPM_FAKE_PWSOCKET");
+}
+#else
+static void
+initPwSocket()
+{
+  char const * resolver = getenv("RPM_FAKE_RESOLVER");
+  if (resolver==0) resolver=RESOLVER_PROG;
+  
+  if (resolver!=0 && *resolver!='\0') {
+    int                        res_sock[2];
+    int                        sync_pipe[2];
+    pid_t              pid;
+    char const *       uid=0;
+    char const *       gid=0;
+
+    uid=getenv("RPM_FAKE_RESOLVER_UID");
+    gid=getenv("RPM_FAKE_RESOLVER_GID");
+
+    if (socketpair(AF_UNIX, SOCK_STREAM, 0, res_sock)==-1 ||
+       pipe(sync_pipe)==-1 ||
+       fcntl(res_sock[0],  F_SETFD, FD_CLOEXEC)==-1 ||
+       fcntl(sync_pipe[0], F_SETFD, FD_CLOEXEC)==-1) {
+      perror(ENSC_WRAPPERS_PREFIX "failed to create/initialize resolver-socket or pipe");
+      exit(255);
+    }
+
+    pid = fork();
+    if (pid==-1) {
+      perror(ENSC_WRAPPERS_PREFIX "fork()");
+      exit(255);
+    }
+
+    if (pid==0) {
+      char const       *args[15];
+      char const       **ptr  = args;
+      char const       *env[] = { "HOME=/", "PATH=/bin:/usr/bin", 0 };
+      char const       *xid_str;
+      
+      setsid();
+      dup2(res_sock[1],  0);
+      dup2(res_sock[1],  1);
+      if (sync_pipe[1]!=3) {
+       close(3);
+       dup2(sync_pipe[1], 3);
+       close(sync_pipe[1]);
+      }
+      close(res_sock[1]);
+       /* ... *socket[0] are marked as close-on-exec ...*/
+
+      *ptr++ = resolver;
+      if (root)  { *ptr++ = "-r"; *ptr++ = root;  }
+      if (uid)   { *ptr++ = "-u"; *ptr++ = uid;   }
+      if (gid)   { *ptr++ = "-g"; *ptr++ = gid;   }
+
+      if (setupContext(ctx, &xid_str)) { *ptr++ = "-s"; }
+      else if (xid_str)                { *ptr++ = "-c"; *ptr++ = xid_str; }
+      
+      *ptr++ = 0;
+      execve(resolver, (char **)args, (char **)env);
+      perror(ENSC_WRAPPERS_PREFIX "failed to exec resolver");
+      exit(255);
+    }
+    else {
+      uint8_t          c;
+
+      close(res_sock[1]);
+      close(sync_pipe[1]);
+      pw_sock   = res_sock[0];
+      sync_sock = sync_pipe[0];
+
+      if (read(sync_sock, &ctx, sizeof ctx)!=sizeof(ctx) ||
+         read(sync_sock, &c, 1)!=1 ||
+         write(pw_sock, ".", 1)!=1 ||
+         read(pw_sock, &c,   1)!=1 ||
+         c!='.') {
+       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "failed to initialize communication with resolver\n");
+       exit(255);
+      }
+
+      if (wait4(pid, 0, WNOHANG,0)==-1) {
+       WRITE_MSG(2, ENSC_WRAPPERS_PREFIX" unexpected initialization-error of resolver\n");
+       exit(255);
+      }
+    }
+
+    unsetenv("RPM_FAKE_RESOLVER_GID");
+    unsetenv("RPM_FAKE_RESOLVER_UID");
+    unsetenv("RPM_FAKE_RESOLVER");    
+  }
+}
+#endif
+
+static void
+initEnvironment()
+{
+  int          syscall_rev;
+  int          syscall_nr;
+  
   if (is_initialized) return;
 
-  initVserverSyscalls(getAndClearEnv("RPM_FAKE_S_CONTEXT_NR", -1));
-  INIT(RTLD_NEXT, execv);
-    //INIT(RTLD_NEXT, open);
+  syscall_rev = getAndClearEnv("RPM_FAKE_S_CONTEXT_REV", 0);
+  syscall_nr  = getAndClearEnv("RPM_FAKE_S_CONTEXT_NR",  273);
+  
+#ifdef VC_ENABLE_API_LEGACY
+  {
+    extern void vc_init_internal_legacy(int ctx_rev, int ctx_number,
+                                       int ipv4_rev, int ipv4_number);
+  
+    vc_init_internal_legacy(syscall_rev, syscall_nr, 3, 274);
+  }
+#endif
+
+  ctx       = getAndClearEnv("RPM_FAKE_CTX",  VC_DYNAMIC_XID);
+  caps      = getAndClearEnv("RPM_FAKE_CAP",  ~0x3404040f);
+  flags     = getAndClearEnv("RPM_FAKE_FLAGS", 0);
+  root      = getenv("RPM_FAKE_CHROOT");
+  mnts      = getenv("RPM_FAKE_NAMESPACE_MOUNTS");
+  if (mnts && *mnts) mnts = strdup(mnts);
+  else               mnts = 0;
+
+  unsetenv("RPM_FAKE_CHROOT");
+  unsetenv("RPM_FAKE_NAMESPACE_MOUNTS");
 
+  
   is_initialized = true;
 }
 
 static void
+initSymbols()
+{
+  INIT(RTLD_NEXT, execv);
+  INIT(RTLD_NEXT, getgrnam);
+  INIT(RTLD_NEXT, getpwnam);
+  INIT(RTLD_NEXT, endpwent);
+  INIT(RTLD_NEXT, endgrent);
+}
+
+static void
 fixPreloadEnv()
 {
   char                 *env = getenv("LD_PRELOAD");
   char                 *pos;
 
-    // the const <-> non-const assignment is not an issue since the following modifying operations
-    // will not be executed in the const-case
+    // the const <-> non-const assignment is not an issue since the following
+    // modifying operations will not be executed in the const-case
   env = env ? env : "";
   pos = strstr(env, LIBNAME);
 
@@ -142,7 +397,7 @@ fixPreloadEnv()
     char       *end_pos = pos + sizeof(LIBNAME);
     bool       is_end = (end_pos[-1]=='\0');
     char       *start_pos;
-    
+
     end_pos[-1] = '\0';
     start_pos   = strrchr(env, ':');
     if (start_pos==0) start_pos = env;
@@ -153,25 +408,138 @@ fixPreloadEnv()
   }
 
 #ifdef DEBUG
-  printf("env='%s'\n", env);
+  if (isDbgLevel(DBG_VERBOSE1|DBG_VARIABLES)) {
+    WRITE_MSG(2, "env='");
+    WRITE_STR(2, env);
+    WRITE_MSG(2, "'\n");
+  }
 #endif
 
   if (*env=='\0') unsetenv("LD_PRELOAD");
 }
 
+void
+initRPMFake()
+{
+  if (getenv("RPM_FAKE_VERSION")) showVersion();
+  if (getenv("RPM_FAKE_HELP"))    showHelp();
+  
+  debug_level = getAndClearEnv("RPM_FAKE_DEBUG", 0);
+
+  if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> initRPMFake <<<<<\n");
+  
+  fixPreloadEnv();
+  initSymbols();
+  initEnvironment();
+  initPwSocket();
+
+#if 0
+  if (isDbgLevel(DBG_VARIABLES|DBG_VERBOSE2)) {
+    
+  }
+#endif
+}
+
+void
+exitRPMFake()
+{ 
+  if (isDbgLevel(DBG_INIT)) WRITE_MSG(2, ">>>>> exitRPMFake <<<<<\n");
+  if (pw_sock!=-1) {
+    uint8_t    c;
+    read(sync_sock, &c, 1);
+    write(pw_sock, "Q", 1);
+  }
+}
+
+
+  //============   the worker part   ===========
+
+
+static bool
+doPwStringRequest(uint32_t *result, char style, char const *name)
+{
+  uint32_t     len = strlen(name);
+  uint8_t      code;
+  uint8_t      c;
+
+    // read the token...
+  read(sync_sock, &c, 1);
+
+  write(pw_sock, &style, 1);
+  write(pw_sock, &len,   sizeof len);
+  write(pw_sock, name,   len);
+  read (pw_sock, &code,  sizeof code);
+  read (pw_sock, result, sizeof *result);
+
+  return code!=0;
+}
+
+struct passwd *
+getpwnam(const char * name)
+{
+  if (pw_sock==-1) return getpwnam_func(name);
+  else {
+    static struct passwd       res = {
+      .pw_passwd = "*",
+      .pw_gid    = -1,
+      .pw_gecos  = "",
+      .pw_dir    = "/",
+      .pw_shell  = "/bin/false"
+    };
+
+    res.pw_name = (char *)(name);
+    if (!doPwStringRequest(&res.pw_uid, 'P', name)) return 0;
+    
+    return &res;
+  }
+}
+
+struct group *
+getgrnam(const char * name)
+{
+  if (pw_sock==-1) return getgrnam_func(name);
+  else {
+    static struct group                res = {
+      .gr_passwd = "*",
+      .gr_mem    = 0
+    };
+
+    res.gr_name = (char *)(name);
+    if (!doPwStringRequest(&res.gr_gid, 'G', name)) return 0;
+
+    return &res;
+  }
+}
+
+void
+endgrent()
+{
+  if (pw_sock==-1) endgrent_func();
+}
+
+void
+endpwent()
+{
+  if (pw_sock==-1) endpwent_func();
+}
+
+
 static int
 execvWorker(char const *path, char * const argv[])
 {
-  int          res;
-  int          ctx;
-
-  ctx = getAndClearEnv("RPM_FAKE_CTX",  -1);
-  if ( (res=new_s_context(getAndClearEnv("RPM_FAKE_S_CONTEXT_REV", 0),
-                         ctx==-1 ? 0 : 1,
-                         &ctx,
-                         getAndClearEnv("RPM_FAKE_CAP",  ~0x3404040f),
-                         getAndClearEnv("RPM_FAKE_FLAGS", 0)))!=-1 &&
-       (res=execv_func(path, argv)!=-1) ) {}
+  int          res = -1;
+
+  if (vc_isSupported(vcFEATURE_MIGRATE))
+    res = vc_ctx_migrate(ctx);
+  else
+#ifdef VC_ENABLE_API_COMPAT  
+    res = vc_new_s_context(ctx,caps,flags);
+#else
+    WRITE_MSG(2, ENSC_WRAPPERS_PREFIX "can not change context: migrate kernel feature missing and 'compat' API disabled\n");
+#endif
+    
+  if (res!=-1)
+    res=execv_func(path, argv);
 
   return res;
 }
@@ -184,9 +552,8 @@ struct ExecvParams
 };
 
 static int
-removeNamespaceMountsChild(void *params_v)
+removeNamespaceMountsChild(struct ExecvParams const *params)
 {
-  struct ExecvParams * params = params_v;
   char                 buf[strlen(params->mnts)+1], *ptr;
 
   strcpy(buf, params->mnts);
@@ -203,46 +570,54 @@ removeNamespaceMountsChild(void *params_v)
     ptr = strtok(0, ":");
   }
 
-  unsetenv("RPM_FAKE_NAMESPACE_MOUNTS");
   return execvWorker(params->path, params->argv);
 }
 
 static int
 removeNamespaceMounts(char const *path, char * const argv[])
 {
-  char const * mnts = getenv("RPM_FAKE_NAMESPACE_MOUNTS");
-
   if (mnts==0) return execvWorker(path, argv);
 
   {
-    char       buf[512 + 2*strlen(mnts)];
-    int                status;
-    pid_t      p;
-    struct ExecvParams         params = {
-      .path = path,
-      .argv = argv,
-      .mnts = mnts,
-    };
-    
-    pid_t      pid = clone(removeNamespaceMountsChild, buf+sizeof(buf)/2,
-                           CLONE_NEWNS|SIGCHLD|CLONE_VFORK, &params);
+    int                                status;
+    pid_t                      p, pid;
+    struct ExecvParams         params;
+
+    params.path = path;
+    params.argv = argv;
+    params.mnts = mnts;
+
+      // the rpmlib signal-handler is still active; use the default one to
+      // make wait4() working...
+    signal(SIGCHLD, SIG_DFL);
+
+#ifdef NDEBUG
+    pid = sys_clone(CLONE_NEWNS|SIGCHLD|CLONE_VFORK, 0);
+#else
+    pid = sys_clone(CLONE_NEWNS|SIGCHLD, 0);
+#endif
 
-    if (pid==-1) return -1;
-    while ((p=waitpid(pid, &status, 0))==-1 &&
+    switch (pid) {
+      case -1  :  return -1;
+      case 0   :  _exit(removeNamespaceMountsChild(&params));
+      default  :  break;
+    }
+       
+    while ((p=wait4(pid, &status, 0,0))==-1 &&
           (errno==EINTR || errno==EAGAIN)) ;
+
     if (p==-1)   return -1;
 
-    if (WIFEXITED(status))   exit(WEXITSTATUS(status));
+    if (WIFEXITED(status))   _exit(WEXITSTATUS(status));
     if (WIFSIGNALED(status)) kill(getpid(), WTERMSIG(status));
 
     return -1;
   }
 }
 
+
 int
 execv(char const *path, char * const argv[]) __THROW
 {
-  initLib();
-  fixPreloadEnv();
   return removeNamespaceMounts(path, argv);
 }