431763b59be0db3c7a135c63ea25d94fcdea3f3b
[util-vserver.git] / util-vserver / src / chcontext.c
1 // $Id$
2
3 // Copyright (C) 2003,2004 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // based on chcontext.cc by Jacques Gelinas
5 //  
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 2, or (at your option)
9 // any later version.
10 //  
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //  
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 /*
21         chcontext is a wrapper to user the new_s_context system call. It
22         does little more than mapping command line option to the system call
23         arguments.
24 */
25 #ifdef HAVE_CONFIG_H
26 #  include <config.h>
27 #endif
28
29 #include "util.h"
30 #include "vserver.h"
31 #include "wrappers.h"
32 #include "wrappers-vserver.h"
33 #include "internal.h"
34
35 #include <stdio.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <errno.h>
40 #include <getopt.h>
41 #include <assert.h>
42
43 #define CMD_HELP        0x1000
44 #define CMD_VERSION     0x1001
45 #define CMD_CAP         0x4000
46 #define CMD_CTX         0x4001
47 #define CMD_DISCONNECT  0x4002
48 #define CMD_DOMAINNAME  0x4003
49 #define CMD_FLAG        0x4004
50 #define CMD_HOSTNAME    0x4005
51 #define CMD_SECURE      0x4006
52 #define CMD_SILENT      0x4007
53
54 int wrapper_exit_code   = 255;
55
56 struct option const
57 CMDLINE_OPTIONS[] = {
58   { "help",     no_argument,  0, CMD_HELP },
59   { "version",  no_argument,  0, CMD_VERSION },
60   { "cap",        required_argument,  0, CMD_CAP },
61   { "ctx",        required_argument,  0, CMD_CTX },
62   { "disconnect", no_argument,        0, CMD_DISCONNECT },
63   { "domainname", required_argument,  0, CMD_DOMAINNAME },
64   { "flag",       required_argument,  0, CMD_FLAG },
65   { "hostname",   required_argument,  0, CMD_HOSTNAME },
66   { "secure",     no_argument,        0, CMD_SECURE },
67   { "silent",     no_argument,        0, CMD_SILENT },
68   { 0,0,0,0 }
69 };
70
71 struct Arguments {
72     size_t              nbctx;
73     xid_t               ctxs[16];
74     bool                do_disconnect;
75     bool                do_silent;
76     unsigned int        flags;
77     uint32_t            remove_caps;
78     uint32_t            add_caps;
79     char const *        hostname;
80     char const *        domainname;
81 };
82
83 static struct Arguments const *         global_args = 0;
84
85 static void
86 showHelp(int fd, char const *cmd, int res)
87 {
88   WRITE_MSG(fd, "Usage: ");
89   WRITE_STR(fd, cmd);
90   WRITE_MSG(fd,
91             " [--cap [!]<cap_name>] [--secure] [--ctx <num>] [--disconnect]\n"
92             "       [--domainname <name>] [--hostname <name>] [--flag <flags>+]\n"
93             "       [--silent] [--] command arguments ...\n"
94             "\n"
95             "chcontext allocate a new security context and executes\n"
96             "a command in that context.\n"
97             "By default, a new/unused context is allocated\n"
98             "\n"
99             "--cap CAP_NAME\n"
100             "    Add a capability from the command. This option may be\n"
101             "    repeated several time.\n"
102             "    See /usr/include/linux/capability.h\n"
103             "    In general, this option is used with the --secure option\n"
104             "    --secure removes most critical capabilities and --cap\n"
105             "    adds specific ones.\n"
106             "\n"
107
108             "--cap !CAP_NAME\n"
109             "    Remove a capability from the command. This option may be\n"
110             "    repeated several time.\n"
111             "    See /usr/include/linux/capability.h\n"
112             "\n"
113             "--ctx num\n"
114             "    Select the context. On root in context 0 is allowed to\n"
115             "    select a specific context.\n"
116             "    Context number 1 is special. It can see all processes\n"
117             "    in any contexts, but can't kill them though.\n"
118             "    Option --ctx may be repeated several times to specify up to 16 contexts.\n"
119
120             "--disconnect\n"
121             "    Start the command in background and make the process\n"
122             "    a child of process 1.\n"
123
124             "--domainname new_domainname\n"
125             "    Set the domainname (NIS) in the new security context.\n"
126             "    Use \"none\" to unset the domain name.\n"
127
128             "--flag\n"
129             "    Set one flag in the new or current security context. The following\n"
130             "    flags are supported. The option may be used several time.\n"
131             "\n"
132             "        fakeinit: The new process will believe it is process number 1.\n"
133             "                  Useful to run a real /sbin/init in a vserver.\n"
134             "        lock:     The new process is trapped and can't use chcontext anymore.\n"
135             "        sched:    The new process and its children will share a common \n"
136             "                  execution priority.\n"
137             "        nproc:    Limit the number of process in the vserver according to\n"
138             "                  ulimit setting. Normally, ulimit is a per user thing.\n"
139             "                  With this flag, it becomes a per vserver thing.\n"
140             "        private:  No one can join this security context once created.\n"
141             "        ulimit:   Apply the current ulimit to the whole context\n"
142
143             "--hostname new_hostname\n"
144             "    Set the hostname in the new security context\n"
145             "    This is need because if you create a less privileged\n"
146             "    security context, it may be unable to change its hostname\n"
147
148             "--secure\n"
149             "    Remove all the capabilities to make a virtual server trustable\n"
150
151             "--silent\n"
152             "    Do not print the allocated context number.\n"
153             "\n"
154             "Please report bugs to " PACKAGE_BUGREPORT "\n");
155
156   exit(res);
157 }
158
159 static void
160 showVersion()
161 {
162   WRITE_MSG(1,
163             "chcontext " VERSION " -- allocates/enters a security context\n"
164             "This program is part of " PACKAGE_STRING "\n\n"
165             "Copyright (C) 2003,2004 Enrico Scholz\n"
166             VERSION_COPYRIGHT_DISCLAIMER);
167   exit(0);
168 }
169
170 static inline void
171 setCap(char const *str, uint32_t *add_caps, uint32_t *remove_caps)
172 {
173   uint32_t      *cap;
174   int           bit;
175   
176   if (str[0] != '!')
177     cap = add_caps;
178   else {
179     cap = remove_caps;
180     str++;
181   }
182         
183   bit = vc_text2cap(str);
184         
185   if (bit!=-1) *cap |= (1<<bit);
186   else {
187     WRITE_MSG(2, "Unknown capability '");
188     WRITE_STR(2, str);
189     WRITE_MSG(2, "'\n");
190     exit(255);
191   }
192 }
193
194 static inline void
195 setFlags(char const *str, uint32_t *flags)
196 {
197   for (;;) {
198     char const  *ptr = strchr(str, ',');
199     size_t      cnt  = ptr ? (size_t)(ptr-str) : strlen(str);
200     
201     if      (strncmp(str, "lock",     cnt)==0) *flags |= S_CTX_INFO_LOCK;
202     else if (strncmp(str, "sched",    cnt)==0) *flags |= S_CTX_INFO_SCHED;
203     else if (strncmp(str, "nproc",    cnt)==0) *flags |= S_CTX_INFO_NPROC;
204     else if (strncmp(str, "private",  cnt)==0) *flags |= S_CTX_INFO_PRIVATE;
205     else if (strncmp(str, "fakeinit", cnt)==0) *flags |= S_CTX_INFO_INIT;
206     else if (strncmp(str, "hideinfo", cnt)==0) *flags |= S_CTX_INFO_HIDEINFO;
207     else if (strncmp(str, "ulimit",   cnt)==0) *flags |= S_CTX_INFO_ULIMIT;
208     else {
209       WRITE_MSG(2, "Unknown flag '");
210       write(2, str, cnt);
211       WRITE_MSG(2, "'\n");
212       exit(255);
213     }
214
215     
216     if (ptr==0) break;
217     str = ptr+1;
218   }
219 }
220
221 static inline ALWAYSINLINE void
222 setHostname(char const *name)
223 {
224   if (name == NULL) return;
225   
226   if (sethostname(name, strlen(name))==-1) {
227     perror("sethostname()");
228     exit(255);
229   }
230   if (!global_args->do_silent) {
231     WRITE_MSG(1, "Host name is now ");
232     WRITE_STR(1, name);
233     WRITE_MSG(1, "\n");
234   }
235 }
236
237 static inline ALWAYSINLINE void
238 setDomainname(char const *name)
239 {
240   if (name == NULL) return;
241   
242   if (setdomainname(name, strlen(name))==-1) {
243     perror("setdomainname()");
244     exit(255);
245   }
246   if (!global_args->do_silent) {
247     WRITE_MSG(1, "Domain name is now ");
248     WRITE_STR(1, name);
249     WRITE_MSG(1, "\n");
250   }
251 }
252
253 static inline ALWAYSINLINE void
254 tellContext(xid_t ctx)
255 {
256   char          buf[sizeof(xid_t)*3+2];
257   size_t        l;
258
259   if (global_args->do_silent) return;
260
261   l = utilvserver_fmt_long(buf,ctx);
262
263   WRITE_MSG(2, "New security context is ");
264   write(2, buf, l);
265   WRITE_MSG(2, "\n");
266 }
267
268 static inline ALWAYSINLINE int
269 initSync(int p[2])
270 {
271   if (!global_args->do_disconnect) return 0;
272
273   Epipe(p);
274   fcntl(p[1], F_SETFD, FD_CLOEXEC);
275   return Efork();
276 }
277
278 static inline ALWAYSINLINE void
279 doSyncStage1(int p[2])
280 {
281   int   fd;
282
283   if (!global_args->do_disconnect) return;
284   
285   fd = Eopen("/dev/null", O_RDONLY, 0);
286   Esetsid();
287   Edup2(fd, 0);
288   Eclose(p[0]);
289   if (fd!=0) Eclose(fd);
290   Ewrite(p[1], ".", 1);
291 }
292
293 static inline ALWAYSINLINE void
294 doSyncStage2(int p[2])
295 {
296   if (!global_args->do_disconnect) return;
297
298   Ewrite(p[1], "X", 1);
299 }
300
301 static void
302 waitOnSync(pid_t pid, int p[2])
303 {
304   int           c;
305   size_t        l;
306
307   assert(global_args->do_disconnect);
308   assert(pid!=0);
309
310   Eclose(p[1]);
311   l = Eread(p[0], &c, 1);
312   if (l!=1) exitLikeProcess(pid);
313   l = Eread(p[0], &c, 1);
314   if (l!=0) exitLikeProcess(pid);
315 }
316
317 int main (int argc, char *argv[])
318 {
319   struct Arguments args = {
320     .nbctx         = 0,
321     .do_disconnect = false,
322     .do_silent     = false,
323     .flags         = 0,
324     .remove_caps   = 0,
325     .add_caps      = 0,
326     .hostname      = 0,
327     .domainname    = 0
328   };
329   xid_t         newctx;
330   int           xflags;
331   int           p[2];
332   pid_t         pid;
333   
334   global_args = &args;
335   
336   while (1) {
337     int         c = getopt_long(argc, argv, "+", CMDLINE_OPTIONS, 0);
338     if (c==-1) break;
339
340     switch (c) {
341       case CMD_HELP             :  showHelp(1, argv[0], 0);
342       case CMD_VERSION          :  showVersion();
343       case CMD_DISCONNECT       :  args.do_disconnect = true;   break;
344       case CMD_SILENT           :  args.do_silent     = true;   break;
345       case CMD_DOMAINNAME       :  args.domainname    = optarg; break;
346       case CMD_HOSTNAME         :  args.hostname      = optarg; break;
347         
348       case CMD_CAP              :
349         setCap(optarg, &args.add_caps, &args.remove_caps);
350         break;
351       case CMD_SECURE           :
352         args.remove_caps |= vc_get_securecaps();
353         break;
354       case CMD_FLAG             :
355         setFlags(optarg, &args.flags);
356         break;
357       case CMD_CTX              :
358         if (args.nbctx>0)
359           WRITE_MSG(2, "WARNING: More than one ctx not supported by this version\n");
360         if (args.nbctx>=DIM_OF(args.ctxs)) {
361           WRITE_MSG(2, "Too many contexts given\n");
362           exit(255);
363         }
364         args.ctxs[args.nbctx++] = atoi(optarg);
365         break;
366
367           
368       default           :
369         WRITE_MSG(2, "Try '");
370         WRITE_STR(2, argv[0]);
371         WRITE_MSG(2, " --help\" for more information.\n");
372         return 255;
373         break;
374     }
375   }
376
377   if (optind>=argc) {
378     WRITE_MSG(2, "No command given; use '--help' for more information.\n");
379     exit(255);
380   }
381   
382   if (args.domainname && strcmp(args.domainname, "none")==0)
383     args.domainname = "";
384     
385   if (args.nbctx == 0)
386     args.ctxs[args.nbctx++] = VC_RANDCTX;
387     
388   xflags = args.flags & 16;
389
390   newctx            = Evc_new_s_context(args.ctxs[0],0,args.flags&~16);
391   args.remove_caps &= (~args.add_caps);
392   setHostname(args.hostname);
393   setDomainname(args.domainname);
394
395   pid = initSync(p);
396   
397   if (pid==0) {
398     if (args.remove_caps!=0 || xflags!=0)
399       Evc_new_s_context (VC_SAMECTX,args.remove_caps,xflags);
400     tellContext(args.ctxs[0]==VC_RANDCTX ? newctx : args.ctxs[0]);
401
402     doSyncStage1(p);
403     execvp (argv[optind],argv+optind);
404     doSyncStage2(p);
405
406     perror("execvp()");
407     exit(255);
408   }
409
410   waitOnSync(pid, p);
411   return EXIT_SUCCESS;
412 }
413
414 #ifdef ENSC_TESTSUITE
415 #define FLAG_TEST(STR,EXP) \
416   {                        \
417     uint32_t    flag=0;    \
418     setFlags(STR, &flag);  \
419     assert(flag==(EXP));   \
420   }
421
422 #define CAP_TEST(STR,EXP_ADD,EXP_DEL)           \
423   {                                             \
424     uint32_t    add=0,del=0;                    \
425     setCap(STR, &add, &del);                    \
426     assert(add==(EXP_ADD));                     \
427     assert(del==(EXP_DEL));                     \
428   }
429
430 void
431 test()
432 {
433   FLAG_TEST("lock",       1);
434   FLAG_TEST("lock,sched", 3);
435
436   CAP_TEST("CHOWN",      1, 0);
437   CAP_TEST("CAP_CHOWN",  1, 0);
438   CAP_TEST("!CHOWN",     0, 1);
439   CAP_TEST("!CAP_CHOWN", 0, 1);
440 }
441 #endif