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