Resource limit information is not kept in the kernel for contexts that do not have
[util-vserver.git] / src / vsh.c
1 /*
2  * Marc E. Fiuczynski <mef@cs.princeton.edu>
3  *
4  * Copyright (c) 2004 The Trustees of Princeton University (Trustees).
5  *
6  * vsh is free software; you can redistribute it and/or modify it
7  * 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  * vsh is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13  * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
14  * License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Poptop; see the file COPYING.  If not, write to the Free
18  * Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include <config.h>
24 #endif
25 #include "compat.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <errno.h>
32 #include <pwd.h>
33 #include <unistd.h>
34 #include <syscall.h>
35 #include <sys/syscall.h>
36 #include <asm/unistd.h>
37 #include <sys/mount.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <fcntl.h>
41 #include <ctype.h>
42 #include <stdarg.h>
43
44 //--------------------------------------------------------------------
45 #include "vserver.h"
46
47 #undef CONFIG_VSERVER_LEGACY
48
49 /* Null byte made explicit */
50 #define NULLBYTE_SIZE                    1
51
52 /* Base for all vserver roots for chroot */
53 #define VSERVER_ROOT_BASE       "/vservers"
54
55 static int
56 _PERROR(const char *format, char *file, int line, int _errno, ...)
57 {
58         va_list ap;
59
60         va_start(ap, _errno);
61         fprintf(stderr, "%s:%d: ", file, line);
62         vfprintf(stderr, format, ap);
63         if (_errno)
64                 fprintf(stderr, ": %s (%d)", strerror(_errno), _errno);
65         fputs("\n", stderr);
66         fflush(stderr);
67
68         return _errno;
69 }
70
71 #define PERROR(format, args...) _PERROR(format, __FILE__, __LINE__, errno, ## args)
72
73 /* Change to root:root (before entering new context) */
74 static int setuidgid_root()
75 {
76         if (setgid(0) < 0) {
77                 PERROR("setgid(0)");
78                 return -1;
79         }
80         if (setuid(0) < 0) {
81                 PERROR("setuid(0)");
82                 return -1;
83         }
84         return 0;
85 }
86
87 static void compute_new_root(char *base, char **root, uid_t uid)
88 {
89         int             root_len;
90         struct passwd   *pwd;
91
92         if ((pwd = getpwuid(uid)) == NULL) {
93                 PERROR("getpwuid(%d)", uid);
94                 exit(1);
95         }
96
97         root_len = 
98                 strlen(base) + strlen("/") +
99                 strlen(pwd->pw_name)      + NULLBYTE_SIZE;
100         (*root) = (char *)malloc(root_len);
101         if ((*root) == NULL) {
102                 PERROR("malloc(%d)", root_len);
103                 exit(1);
104         }
105     
106         sprintf((*root), "%s/%s", base, pwd->pw_name);
107         (*root)[root_len - 1] = '\0';
108 }
109
110 /* Example: sandbox_root = /vservers/bnc, relpath = /proc/1 */
111 static int sandbox_file_exists(char *sandbox_root, char *relpath)
112 {
113         struct stat stat_buf;
114         char   *file;
115         int    len, exists = 0;
116
117         len = strlen(sandbox_root) + strlen(relpath) + NULLBYTE_SIZE;
118         if ((file = (char *)malloc(len)) == NULL) {
119                 PERROR("malloc(%d)", len);
120                 exit(1);
121         }
122         sprintf(file, "%s%s", sandbox_root, relpath);
123         file[len - 1] = '\0';
124         if (stat(file, &stat_buf) == 0) {
125                 exists = 1;
126         }
127
128
129         free(file);
130         return exists;
131 }
132
133 static int proc_mounted(char *sandbox_root)
134 {
135         return sandbox_file_exists(sandbox_root, "/proc/1");
136 }
137
138 static int devpts_mounted(char *sandbox_root)
139 {
140         return sandbox_file_exists(sandbox_root, "/dev/pts/0");
141 }
142
143 static void mount_proc(char *sandbox_root)
144 {
145         char        *source = "/proc";
146         char        *target;
147         int         len;
148
149         len = strlen(sandbox_root) + strlen("/") + strlen("proc") + NULLBYTE_SIZE;
150         if ((target = (char *)malloc(len)) == NULL) {
151                 PERROR("malloc(%d)", len);
152                 exit(1);
153         }
154
155         sprintf(target, "%s/proc", sandbox_root);
156         target[len - 1] = '\0';
157         if (!proc_mounted(sandbox_root))
158                 mount(source, target, "proc", MS_BIND | MS_RDONLY, NULL);
159
160         free(target);
161 }
162
163 static void mount_devpts(char *sandbox_root)
164 {
165         char        *source = "/dev/pts";
166         char        *target;
167         int         len;
168     
169         len = strlen(sandbox_root) + strlen("/") + strlen("dev/pts") + NULLBYTE_SIZE;
170         if ((target = (char *)malloc(len)) == NULL) {
171                 PERROR("malloc(%d)", len);
172                 exit(1);
173         }
174
175         sprintf(target, "%s/dev/pts", sandbox_root);
176         target[len - 1] = '\0';
177         if (!devpts_mounted(sandbox_root))
178                 mount(source, target, "devpts", 0, NULL);
179
180         free(target);
181 }
182
183 static int sandbox_chroot(uid_t uid)
184 {
185         char *sandbox_root = NULL;
186
187         compute_new_root(VSERVER_ROOT_BASE,&sandbox_root, uid);
188         mount_proc(sandbox_root);
189         mount_devpts(sandbox_root);
190         if (chroot(sandbox_root) < 0) {
191                 PERROR("chroot(%s)", sandbox_root);
192                 exit(1);
193         }
194         if (chdir("/") < 0) {
195                 PERROR("chdir(/)");
196                 exit(1);
197         }
198         return 0;
199 }
200
201 #define WHITESPACE(buffer,index,len)     \
202   while(isspace((int)buffer[index])) \
203         if (index < len) index++; else goto out;
204
205 struct resources {
206         char *name;
207         int *limit;
208 };
209
210 #define VSERVERCONF "/etc/vservers/"
211 static void get_limits(char *context, int *cpu, int *mem, int *task) {
212         FILE *fb;
213         size_t len = strlen(VSERVERCONF) + strlen(context) + strlen(".conf") + NULLBYTE_SIZE;
214         char *conf = (char *)malloc(len);       
215         struct resources list[] = 
216                 {{"MEMLIMIT", mem},
217                  {"CPULIMIT", cpu},
218                  {"TASKLIMIT", task},
219                  {0,0}};
220         struct resources *r;
221
222         sprintf(conf, "%s%s.conf", VSERVERCONF, context);
223
224         /* open the conf file for reading */
225         fb = fopen(conf,"r");
226         if (fb != NULL) {
227                 size_t index;
228                 char *buffer = malloc(1000);
229                 char *p;
230
231                 /* the conf file exist */ 
232                 while((p=fgets(buffer,1000-1,fb))!=NULL) {
233                         index = 0;
234                         len = strnlen(buffer,1000);
235                         WHITESPACE(buffer,index,len);
236                         if (buffer[index] == '#') 
237                                 continue;
238
239                         for (r=list; r->name; r++)
240                                 if ((p=strstr(&buffer[index],r->name))!=NULL) {
241                                         /* adjust index into buffer */
242                                         index+= (p-&buffer[index])+strlen(r->name);
243
244                                         /* skip over whitespace */
245                                         WHITESPACE(buffer,index,len);
246
247                                         /* expecting to see = sign */
248                                         if (buffer[index++]!='=') goto out;
249
250                                         /* skip over whitespace */
251                                         WHITESPACE(buffer,index,len);
252
253                                         /* expecting to see a digit for number */
254                                         if (!isdigit((int)buffer[index])) goto out;
255
256                                         *r->limit = atoi(&buffer[index]);
257                                         break;
258                                 }
259                 }
260         out:
261                 free(buffer);
262         } else {
263                 fprintf(stderr,"cannot open %s\n",conf);
264         }
265         free(conf);
266 }
267
268
269 static int sandbox_processes(xid_t xid, char *context)
270 {
271 #ifdef CONFIG_VSERVER_LEGACY
272         int     flags;
273
274         flags = 0;
275         flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
276         /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
277
278         (void) vc_new_s_context(xid, 0, flags);
279
280         /* use legacy dirty hack for capremove */
281         if (vc_new_s_context(VC_SAMECTX, vc_get_insecurebcaps(), flags) == VC_NOCTX) {
282                 PERROR("vc_new_s_context(%u, 0x%16ullx, 0x%08x)",
283                        VC_SAMECTX, vc_get_insecurebcaps(), flags);
284                 exit(1);
285         }
286 #else
287         struct vc_ctx_caps caps;
288         struct vc_ctx_flags flags;
289         int cpu = VC_LIM_KEEP;
290         int mem = VC_LIM_KEEP;
291         int task = VC_LIM_KEEP;
292         get_limits(context,&cpu, &mem, &task);
293         (void) (sandbox_chroot(xid));
294
295         caps.ccaps = ~vc_get_insecureccaps();
296         caps.cmask = ~0ull;
297         caps.bcaps = ~vc_get_insecurebcaps();
298         caps.bmask = ~0ull;
299
300         flags.flagword = VC_VXF_INFO_LOCK;
301         flags.mask = VC_VXF_STATE_SETUP | VC_VXF_INFO_LOCK;
302
303         errno = 0;
304         if ((vc_ctx_create(xid) == VC_NOCTX) && (errno != EEXIST)) {
305                 PERROR("vc_ctx_create(%d)", xid);
306                 exit(1);
307         }
308
309         if (errno != EEXIST) {
310                 struct vc_rlimit limits;
311                 /* The context did not exist before, which requires that we set the various limit */
312
313                 /* CPU    */
314                 /* not yet */
315
316                 /* MEM    */
317                 limits.min  = VC_LIM_KEEP;
318                 limits.soft = VC_LIM_KEEP;
319                 limits.hard = mem;
320                 if (vc_set_rlimit(xid, 5, &limits)) {
321                 }
322         
323                 /* TASK   */
324                 limits.min  = VC_LIM_KEEP;
325                 limits.soft = VC_LIM_KEEP;
326                 limits.hard = task;
327                 if (vc_set_rlimit(xid, 6, &limits)) {
328                         /* setting limit failed */
329                 }
330         }
331         
332         if (vc_set_ccaps(xid, &caps) == -1) {
333                 PERROR("vc_set_ccaps(%d, 0x%16ullx/0x%16ullx, 0x%16ullx/0x%16ullx)\n",
334                        xid, caps.ccaps, caps.cmask, caps.bcaps, caps.bmask);
335                 exit(1);
336         }
337
338         if (vc_set_cflags(xid, &flags) == -1) {
339                 PERROR("vc_set_cflags(%d, 0x%16llx/0x%16llx)\n",
340                        xid, flags.flagword, flags.mask);
341                 exit(1);
342         }
343 #endif
344         return 0;
345 }
346
347
348 void runas_slice_user(char *username)
349 {
350         struct passwd pwdd, *pwd = &pwdd, *result;
351         char          *pwdBuffer;
352         char          *home_env, *logname_env, *mail_env, *shell_env, *user_env;
353         int           home_len, logname_len, mail_len, shell_len, user_len;
354         long          pwdBuffer_len;
355         static char   *envp[10];
356
357
358         pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
359         if (pwdBuffer_len == -1) {
360                 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
361                 exit(1);
362         }
363
364         pwdBuffer = (char*)malloc(pwdBuffer_len);
365         if (pwdBuffer == NULL) {
366                 PERROR("malloc(%d)", pwdBuffer_len);
367                 exit(1);
368         }
369
370         errno = 0;
371         if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
372                 PERROR("getpwnam_r(%s)", username);
373                 exit(1);
374         }
375
376         if (setgid(pwd->pw_gid) < 0) {
377                 PERROR("setgid(%d)", pwd->pw_gid);
378                 exit(1);
379         }
380
381         if (setuid(pwd->pw_uid) < 0) {
382                 PERROR("setuid(%d)", pwd->pw_uid);
383                 exit(1);
384         }
385
386         if (chdir(pwd->pw_dir) < 0) {
387                 PERROR("chdir(%s)", pwd->pw_dir);
388                 exit(1);
389         }
390
391         home_len    = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
392         logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
393         mail_len    = strlen("MAIL=/var/spool/mail/") + strlen(username) 
394                 + NULLBYTE_SIZE;
395         shell_len   = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
396         user_len    = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
397
398         home_env    = (char *)malloc(home_len);
399         logname_env = (char *)malloc(logname_len);
400         mail_env    = (char *)malloc(mail_len);
401         shell_env   = (char *)malloc(shell_len);
402         user_env    = (char *)malloc(user_len);
403
404         if ((home_env    == NULL)  || 
405             (logname_env == NULL)  ||
406             (mail_env    == NULL)  ||
407             (shell_env   == NULL)  ||
408             (user_env    == NULL)) {
409                 PERROR("malloc");
410                 exit(1);
411         }
412
413         sprintf(home_env, "HOME=%s", pwd->pw_dir);
414         sprintf(logname_env, "LOGNAME=%s", username);
415         sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
416         sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
417         sprintf(user_env, "USER=%s", username);
418     
419         home_env[home_len - 1]       = '\0';
420         logname_env[logname_len - 1] = '\0';
421         mail_env[mail_len - 1]       = '\0';
422         shell_env[shell_len - 1]     = '\0';
423         user_env[user_len - 1]       = '\0';
424
425         envp[0] = home_env;
426         envp[1] = logname_env;
427         envp[2] = mail_env;
428         envp[3] = shell_env;
429         envp[4] = user_env;
430         envp[5] = 0;
431
432         if ((putenv(home_env)    < 0) ||
433             (putenv(logname_env) < 0) ||
434             (putenv(mail_env)    < 0) ||
435             (putenv(shell_env)   < 0) ||
436             (putenv(user_env)    < 0)) {
437                 PERROR("vserver: putenv error ");
438                 exit(1);
439         }
440 }
441
442 void slice_enter(char *context)
443 {
444         struct passwd pwdd, *pwd = &pwdd, *result;
445         char          *pwdBuffer;
446         long          pwdBuffer_len;
447         uid_t uid;
448
449         pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
450         if (pwdBuffer_len == -1) {
451                 PERROR("sysconf(_SC_GETPW_R_SIZE_MAX)");
452                 exit(1);
453         }
454
455         pwdBuffer = (char*)malloc(pwdBuffer_len);
456         if (pwdBuffer == NULL) {
457                 PERROR("malloc(%d)", pwdBuffer_len);
458                 exit(1);
459         }
460
461         errno = 0;
462         if ((getpwnam_r(context,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
463                 PERROR("getpwnam_r(%s)", context);
464                 exit(2);
465         }
466         uid = pwd->pw_uid;
467
468         if (setuidgid_root() < 0) { /* For chroot, new_s_context */
469                 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
470                 exit(2);
471         }
472
473 #ifdef CONFIG_VSERVER_LEGACY
474         (void) (sandbox_chroot(uid));
475 #endif
476
477         if (sandbox_processes((xid_t) uid, context) < 0) {
478                 fprintf(stderr, "vsh: Could not change context to %d\n", uid);
479                 exit(2);
480         }
481 }
482
483 //--------------------------------------------------------------------
484
485 #define DEFAULT_SHELL "/bin/sh"
486
487 /* Exit statuses for programs like 'env' that exec other programs.
488    EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs.  */
489 enum
490 {
491   EXIT_CANNOT_INVOKE = 126,
492   EXIT_ENOENT = 127
493 };
494
495 int main(int argc, char **argv)
496 {
497     struct passwd   pwdd, *pwd = &pwdd, *result;
498     char            *context, *username, *shell, *pwdBuffer;
499     long            pwdBuffer_len;
500     uid_t           uid;
501     int             index, i;
502
503     if (argv[0][0]=='-') 
504       index = 1;
505     else
506       index = 0;
507
508     uid = getuid();
509     if ((pwd = getpwuid(uid)) == NULL) {
510       PERROR("getpwuid(%d)", uid);
511       exit(1);
512     }
513
514     context = (char*)strdup(pwd->pw_name);
515     if (!context) {
516       PERROR("strdup");
517       exit(2);
518     }
519
520     /* enter vserver "context" */
521     slice_enter(context);
522
523     /* Now run as username in this context. Note that for PlanetLab's
524        vserver configuration the context name also happens to be the
525        "default" username within the vserver context.
526     */
527     username = context;
528     runas_slice_user(username);
529
530     /* With the uid/gid appropriately set. Let's figure out what the
531      * shell in the vserver's /etc/passwd is for the given username.
532      */
533
534     pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
535     if (pwdBuffer_len == -1) {
536             PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
537             exit(1);
538     }
539     pwdBuffer = (char*)malloc(pwdBuffer_len);
540     if (pwdBuffer == NULL) {
541             PERROR("malloc(%d)", pwdBuffer_len);
542             exit(1);
543     }
544
545     errno = 0;
546     if ((getpwnam_r(username,pwd,pwdBuffer,pwdBuffer_len, &result) != 0) || (errno != 0)) {
547         PERROR("getpwnam_r(%s)", username);
548         exit(1);
549     }
550
551     /* Make sure pw->pw_shell is non-NULL.*/
552     if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
553       pwd->pw_shell = (char *) DEFAULT_SHELL;
554     }
555
556     shell = (char *)strdup(pwd->pw_shell);
557     if (!shell) {
558       PERROR("strdup");
559       exit(2);
560     }
561
562     /* Check whether 'su' or 'sshd' invoked us as a login shell or
563        not; did this above when testing argv[0]=='-'.
564     */
565     argv[0] = shell;
566     if (index == 1) {
567       char **args;
568       args = (char**)malloc(sizeof(char*)*(argc+2));
569       if (!args) {
570         PERROR("malloc(%d)", sizeof(char*)*(argc+2));
571         exit(1);
572       }
573       args[0] = argv[0];
574       args[1] = "-l";
575       for(i=1;i<argc+1;i++) {
576         args[i+1] = argv[i];
577       }
578       argv = args;
579     }
580     (void) execvp(shell,argv);
581     {
582       int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
583       exit (exit_status);
584     }
585
586     return 0; /* shutup compiler */
587 }