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