- CAP_CONTEXT not defined in linuxcaps.h, and is definitely something we
[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
43 //--------------------------------------------------------------------
44 #include "linuxcaps.h"
45 #include "vserver.h"
46
47 /* Null byte made explicit */
48 #define NULLBYTE_SIZE                    1
49
50 /* Base for all vserver roots for chroot */
51 #define VSERVER_ROOT_BASE       "/vservers"
52
53 /* Change to root:root (before entering new context) */
54 static int setuidgid_root()
55 {
56         if (setgid(0) < 0) {
57                 fprintf(stderr, "setgid error\n");
58                 return -1;
59         }
60         if (setuid(0) < 0) {
61                 fprintf(stderr, "setuid error\n");
62                 return -1;
63         }
64         return 0;
65 }
66
67 static void compute_new_root(char *base, char **root, uid_t uid)
68 {
69         int             root_len;
70         struct passwd   *pwd;
71
72         if ((pwd = getpwuid(uid)) == NULL) {
73                 perror("vserver: getpwuid error ");
74                 exit(1);
75         }
76
77         root_len = 
78                 strlen(base) + strlen("/") +
79                 strlen(pwd->pw_name)      + NULLBYTE_SIZE;
80         (*root) = (char *)malloc(root_len);
81         if ((*root) == NULL) {
82                 perror("vserver: malloc error ");
83                 exit(1);
84         }
85     
86         sprintf((*root), "%s/%s", base, pwd->pw_name);
87         (*root)[root_len - 1] = '\0';
88 }
89
90 /* Example: sandbox_root = /vservers/bnc, relpath = /proc/1 */
91 static int sandbox_file_exists(char *sandbox_root, char *relpath)
92 {
93         struct stat stat_buf;
94         char   *file;
95         int    len, exists = 0;
96
97         len = strlen(sandbox_root) + strlen(relpath) + NULLBYTE_SIZE;
98         if ((file = (char *)malloc(len)) == NULL) {
99                 perror("vserver: malloc error ");
100                 exit(1);
101         }
102         sprintf(file, "%s%s", sandbox_root, relpath);
103         file[len - 1] = '\0';
104         if (stat(file, &stat_buf) == 0) {
105                 exists = 1;
106         }
107
108
109         free(file);
110         return exists;
111 }
112
113 static int proc_mounted(char *sandbox_root)
114 {
115         return sandbox_file_exists(sandbox_root, "/proc/1");
116 }
117
118 static int devpts_mounted(char *sandbox_root)
119 {
120         return sandbox_file_exists(sandbox_root, "/dev/pts/0");
121 }
122
123 static void mount_proc(char *sandbox_root,uid_t uid)
124 {
125         char        *source = "/proc";
126         char        *target;
127         int         len;
128
129         len = strlen(sandbox_root) + strlen("/") + strlen("proc") + NULLBYTE_SIZE;
130         if ((target = (char *)malloc(len)) == NULL) {
131                 perror("vserver: malloc error ");
132                 exit(1);
133         }
134
135         sprintf(target, "%s/proc", sandbox_root);
136         target[len - 1] = '\0';
137         if (!proc_mounted(sandbox_root))
138                 mount(source, target, "proc", MS_BIND | MS_RDONLY, NULL);
139
140         free(target);
141 }
142
143 static void mount_devpts(char *sandbox_root)
144 {
145         char        *source = "/dev/pts";
146         char        *target;
147         int         len;
148     
149         len = strlen(sandbox_root) + strlen("/") + strlen("dev/pts") + NULLBYTE_SIZE;
150         if ((target = (char *)malloc(len)) == NULL) {
151                 perror("vserver: malloc error ");
152                 exit(1);
153         }
154
155         sprintf(target, "%s/dev/pts", sandbox_root);
156         target[len - 1] = '\0';
157         if (!devpts_mounted(sandbox_root))
158                 mount(source, target, "devpts", 0, NULL);
159
160         free(target);
161 }
162
163 static int sandbox_chroot(uid_t uid)
164 {
165         char *sandbox_root = NULL;
166
167         compute_new_root(VSERVER_ROOT_BASE,&sandbox_root, uid);
168         mount_proc(sandbox_root,uid);
169         mount_devpts(sandbox_root);
170         if (chroot(sandbox_root) < 0) {
171                 fprintf(stderr,"vserver: chroot error (%s): ",sandbox_root);
172                 perror("");
173                 exit(1);
174         }
175         if (chdir("/") < 0) {
176                 perror("vserver: chdir error ");
177                 exit(1);
178         }
179         return 0;
180 }
181
182 #ifndef CAP_CONTEXT
183 #  define CAP_CONTEXT   29
184 #endif
185
186 static struct {
187         const char *option;
188         int bit;
189 }tbcap[]={
190         // The following capabilities are normally available
191         // to vservers administrator, but are place for
192         // completeness
193         {"CAP_CHOWN",CAP_CHOWN},
194         {"CAP_DAC_OVERRIDE",CAP_DAC_OVERRIDE},
195         {"CAP_DAC_READ_SEARCH",CAP_DAC_READ_SEARCH},
196         {"CAP_FOWNER",CAP_FOWNER},
197         {"CAP_FSETID",CAP_FSETID},
198         {"CAP_KILL",CAP_KILL},
199         {"CAP_SETGID",CAP_SETGID},
200         {"CAP_SETUID",CAP_SETUID},
201         {"CAP_SETPCAP",CAP_SETPCAP},
202         {"CAP_SYS_TTY_CONFIG",CAP_SYS_TTY_CONFIG},
203         {"CAP_LEASE",CAP_LEASE},
204         {"CAP_SYS_CHROOT",CAP_SYS_CHROOT},
205
206         // Those capabilities are not normally available
207         // to vservers because they are not needed and
208         // may represent a security risk
209         {"CAP_LINUX_IMMUTABLE",CAP_LINUX_IMMUTABLE},
210         {"CAP_NET_BIND_SERVICE",CAP_NET_BIND_SERVICE},
211         {"CAP_NET_BROADCAST",CAP_NET_BROADCAST},
212         {"CAP_NET_ADMIN",       CAP_NET_ADMIN},
213         {"CAP_NET_RAW", CAP_NET_RAW},
214         {"CAP_IPC_LOCK",        CAP_IPC_LOCK},
215         {"CAP_IPC_OWNER",       CAP_IPC_OWNER},
216         {"CAP_SYS_MODULE",CAP_SYS_MODULE},
217         {"CAP_SYS_RAWIO",       CAP_SYS_RAWIO},
218         {"CAP_SYS_PACCT",       CAP_SYS_PACCT},
219         {"CAP_SYS_ADMIN",       CAP_SYS_ADMIN},
220         {"CAP_SYS_BOOT",        CAP_SYS_BOOT},
221         {"CAP_SYS_NICE",        CAP_SYS_NICE},
222         {"CAP_SYS_RESOURCE",CAP_SYS_RESOURCE},
223         {"CAP_SYS_TIME",        CAP_SYS_TIME},
224         {"CAP_MKNOD",           CAP_MKNOD},
225         {"CAP_CONTEXT",         CAP_CONTEXT},
226         {NULL,0}
227 };
228
229 #define VSERVERCONF "/etc/vservers/"
230 static unsigned get_remove_cap(char *name) {
231         FILE     *fb;
232         unsigned remove_cap;
233
234         char *vserverconf;
235         int vserverconflen;
236
237         remove_cap = /* NOTE: keep in sync with chcontext.c */
238                 (1<<CAP_LINUX_IMMUTABLE)|
239                 (1<<CAP_NET_BROADCAST)|
240                 (1<<CAP_NET_ADMIN)|
241                 (1<<CAP_NET_RAW)|
242                 (1<<CAP_IPC_LOCK)|
243                 (1<<CAP_IPC_OWNER)|
244                 (1<<CAP_SYS_MODULE)|
245                 (1<<CAP_SYS_RAWIO)|
246                 (1<<CAP_SYS_PACCT)|
247                 (1<<CAP_SYS_ADMIN)|
248                 (1<<CAP_SYS_BOOT)|
249                 (1<<CAP_SYS_NICE)|
250                 (1<<CAP_SYS_RESOURCE)|
251                 (1<<CAP_SYS_TIME)|
252                 (1<<CAP_MKNOD)|
253                 (1<<CAP_CONTEXT)|
254                 0
255                 ;
256
257         /*
258          * find out which capabilities to put back in by reading the conf file 
259          */
260
261         /* construct the pathname to the conf file */
262         vserverconflen = strlen(VSERVERCONF) + strlen(name) + strlen(".conf") + NULLBYTE_SIZE;
263         vserverconf    = (char *)malloc(vserverconflen);        
264         sprintf(vserverconf, "%s%s.conf", VSERVERCONF, name);
265         
266         /* open the conf file for reading */
267         fb = fopen(vserverconf,"r");
268         if (fb != NULL) {
269                 unsigned cap;
270                 size_t index;
271                 size_t len;
272                 char buffer[1000], *p;
273
274                 /* the conf file file exist */ 
275                 while((p=fgets(buffer,sizeof(buffer)-1,fb))!=NULL) {
276
277                         /* walk past leading spaces */
278                         index = 0;
279                         len = strnlen(buffer,sizeof(buffer)-1);
280                         while(isspace((int)buffer[index])) {
281                                 if (index < len) {
282                                         index++;
283                                 } else {
284                                         goto out;
285                                 }
286                         }
287
288                         if (buffer[index] == '#') continue;
289                                 
290                         /* check if it is the S_CAPS */
291                         if ((p=strstr(&buffer[index],"S_CAPS"))!=NULL) {
292                                 int j;
293                                 cap = 0;
294
295                                 /* what follows is a bunch of error
296                                    checking to parse the S_CAPS="..."
297                                    string */
298
299                                 /* adjust index into buffer */
300                                 index+= (p-&buffer[index])+strlen("S_CAPS");
301
302                                 /* skip over whitespace */
303                                 while(isspace((int)buffer[index])) {
304                                         if (index < len) {
305                                                 index++;
306                                         } else {
307                                         /* parse error */
308                                                 goto out;
309                                         }
310                                 }
311
312                                 /* expecting to see = sign */
313                                 if (buffer[index++]!='=') {
314                                         /* parse error */
315                                         goto out;
316                                 }
317
318                                 /* skip over whitespace */
319                                 while(isspace((int)buffer[index])) {
320                                         if (index < len) {
321                                                 index++;
322                                         } else {
323                                                 /* parse error */
324                                                 goto out;
325                                         }
326                                 }
327
328                                 /* expecting to see the opening " */
329                                 if (buffer[index]!='"') {
330                                         /* parse error */
331                                         goto out;
332                                 }
333
334                                 /* check to see that we are still within bounds */
335                                 if (index < len) {
336                                         index++;
337                                 } else {
338                                         /* parse error */
339                                         goto out;
340                                 }
341
342                                 /* search for the closing " */
343                                 if((p=strstr(&buffer[index],"\""))==NULL) {
344                                         /* parse error */
345                                         goto out;
346                                 }
347
348                                 /* ok... we should now have a bunch of
349                                    CAP keys words within the quotes */
350
351                                 for (j=0; tbcap[j].option != NULL; j++){
352                                         if ((p=strstr(buffer,tbcap[j].option))!=NULL){
353                                                 len = strlen(tbcap[j].option);
354                                                 if (((isspace(*(p-1))) || (*(p-1)=='"')) &&
355                                                     ((isspace(*(p+len))) || (*(p+len)=='"'))) {
356                                                         cap |= (1<<tbcap[j].bit);
357                                                 } else {
358                                                         /* parse error */
359                                                         goto out;
360                                                 }
361                                         }
362                                 }
363                                 remove_cap &= ~cap;
364                                 break;
365                         }
366                 }
367  out:
368                 /* close the conf file */
369                 fclose(fb);
370         }
371         return remove_cap;
372 }
373
374 static int sandbox_processes(uid_t uid, unsigned remove_cap)
375 {
376         int      context;
377         int      flags;
378
379         /* Unique context */
380         context = uid;
381
382         flags = 0;
383         flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
384         /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
385
386         if (vc_new_s_context(context,remove_cap,flags) < 0) {
387                 perror("vserver: new_s_context error ");
388                 exit(1);
389         }
390         return 0;
391 }
392
393
394 void runas_slice_user(char *username)
395 {
396         struct passwd *pwd;
397         char          *home_env, *logname_env, *mail_env, *shell_env, *user_env;
398         int           home_len, logname_len, mail_len, shell_len, user_len;
399         static char   *envp[10];
400
401         if ((pwd = getpwnam(username)) == NULL) {
402                 perror("vserver: getpwnam error ");
403                 exit(1);
404         }
405
406         if (setgid(pwd->pw_gid) < 0) {
407                 perror("vserver: setgid error ");
408                 exit(1);
409         }
410
411         if (setuid(pwd->pw_uid) < 0) {
412                 perror("vserver: setuid error ");
413                 exit(1);
414         }
415
416         if (chdir(pwd->pw_dir) < 0) {
417                 perror("vserver: chdir error ");
418                 exit(1);
419         }
420
421         home_len    = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
422         logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
423         mail_len    = strlen("MAIL=/var/spool/mail/") + strlen(username) 
424                 + NULLBYTE_SIZE;
425         shell_len   = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
426         user_len    = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
427
428         home_env    = (char *)malloc(home_len);
429         logname_env = (char *)malloc(logname_len);
430         mail_env    = (char *)malloc(mail_len);
431         shell_env   = (char *)malloc(shell_len);
432         user_env    = (char *)malloc(user_len);
433
434         if ((home_env    == NULL)  || 
435             (logname_env == NULL)  ||
436             (mail_env    == NULL)  ||
437             (shell_env   == NULL)  ||
438             (user_env    == NULL)) {
439                 perror("vserver: malloc error ");
440                 exit(1);
441         }
442
443         sprintf(home_env, "HOME=%s", pwd->pw_dir);
444         sprintf(logname_env, "LOGNAME=%s", username);
445         sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
446         sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
447         sprintf(user_env, "USER=%s", username);
448     
449         home_env[home_len - 1]       = '\0';
450         logname_env[logname_len - 1] = '\0';
451         mail_env[mail_len - 1]       = '\0';
452         shell_env[shell_len - 1]     = '\0';
453         user_env[user_len - 1]       = '\0';
454
455         envp[0] = home_env;
456         envp[1] = logname_env;
457         envp[2] = mail_env;
458         envp[3] = shell_env;
459         envp[4] = user_env;
460         envp[5] = 0;
461
462         if ((putenv(home_env)    < 0) ||
463             (putenv(logname_env) < 0) ||
464             (putenv(mail_env)    < 0) ||
465             (putenv(shell_env)   < 0) ||
466             (putenv(user_env)    < 0)) {
467                 perror("vserver: putenv error ");
468                 exit(1);
469         }
470 }
471
472
473
474 void slice_enter(char *context)
475 {
476         struct passwd   *pwd;
477         unsigned remove_cap;
478         uid_t uid;
479
480         if ((pwd = getpwnam(context)) == NULL) {
481                 fprintf(stderr,"vserver: getpwname(%s) failed",context);
482                 exit(2);
483         }
484
485         context = (char*)malloc(strlen(pwd->pw_name)+NULLBYTE_SIZE);
486         if (!context) {
487                 perror("vserver: malloc failed");
488                 exit(2);
489         }
490         strcpy(context,pwd->pw_name);
491
492         if (setuidgid_root() < 0) { /* For chroot, new_s_context */
493                 fprintf(stderr,"vserver: Could not setuid/setguid to root:root\n");
494                 exit(2);
495         }
496
497         remove_cap = get_remove_cap(context);
498
499         uid = pwd->pw_uid;
500         if (sandbox_chroot(uid) < 0) {
501                 fprintf(stderr, "vserver: Could not chroot to vserver root\n");
502                 exit(2);
503         }
504
505         if (sandbox_processes(uid, remove_cap) < 0) {
506                 fprintf(stderr, "vserver: Could not sandbox processes in vserver\n");
507                 exit(2);
508         }
509 }
510
511 //--------------------------------------------------------------------
512
513 #define DEFAULT_SHELL "/bin/sh"
514
515 /* Exit statuses for programs like 'env' that exec other programs.
516    EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs.  */
517 enum
518 {
519   EXIT_CANNOT_INVOKE = 126,
520   EXIT_ENOENT = 127
521 };
522
523 extern void slice_enter(char *);
524 extern void runas_slice_user(char *);
525
526 int main(int argc, char **argv)
527 {
528     char *context, *username, *shell;
529     struct passwd   *pwd;
530     uid_t           uid;
531     int index, i;
532
533     if (argv[0][0]=='-') 
534       index = 1;
535     else
536       index = 0;
537
538     uid = getuid();
539     if ((pwd = getpwuid(uid)) == NULL) {
540       fprintf(stderr,"vsh: getpwnam error failed for %d\n",uid); 
541       exit(1);
542     }
543
544     context = (char*)strdup(pwd->pw_name);
545     if (!context) {
546       perror("vsh: strdup failed");
547       exit(2);
548     }
549
550     /* enter vserver "context" */
551     slice_enter(context);
552
553     /* Now run as username in this context. Note that for PlanetLab's
554        vserver configuration the context name also happens to be the
555        "default" username within the vserver context.
556     */
557     username = context;
558     runas_slice_user(username);
559
560     /* With the uid/gid appropriately set. Let's figure out what the
561      * shell in the vserver's /etc/passwd is for the given username.
562      */
563     if ((pwd = getpwnam(username)) == NULL) {
564         fprintf(stderr,"vsh: getpwnam error failed for %s\n",username); 
565         exit(1);
566     }
567
568     /* Make sure pw->pw_shell is non-NULL.*/
569     if (pwd->pw_shell == NULL || pwd->pw_shell[0] == '\0') {
570       pwd->pw_shell = (char *) DEFAULT_SHELL;
571     }
572
573     shell = (char *)strdup(pwd->pw_shell);
574     if (!shell) {
575       perror("vsh: strdup failed");
576       exit(2);
577     }
578
579     /* Check whether 'su' or 'sshd' invoked us as a login shell or
580        not; did this above when testing argv[0]=='-'.
581     */
582     argv[0] = shell;
583     if (index == 1) {
584       char **args;
585       args = (char**)malloc(sizeof(char*)*(argc+2));
586       if (!args) {
587         perror("vsh: malloc failed");
588       }
589       args[0] = argv[0];
590       args[1] = "-l";
591       for(i=1;i<argc+1;i++) {
592         args[i+1] = argv[i];
593       }
594       argv = args;
595     }
596     (void) execvp(shell,argv);
597     {
598       int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
599       exit (exit_status);
600     }
601
602     return 0; /* shutup compiler */
603 }