contd...
[util-vserver-pl.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
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30 #include <limits.h>
31 #include <pwd.h>
32 #include <unistd.h>
33 #include <syscall.h>
34 #include <sys/syscall.h>
35 #include <asm/unistd.h>
36 #include <sys/mount.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/resource.h>
40 #include <fcntl.h>
41 #include <ctype.h>
42 #include <stdarg.h>
43
44 //--------------------------------------------------------------------
45 #include <vserver.h>
46 #include "planetlab.h"
47
48 /* Change to root:root (before entering new context) */
49 static int setuidgid_root()
50 {
51         if (setgid(0) < 0) {
52                 PERROR("setgid(0)");
53                 return -1;
54         }
55         if (setuid(0) < 0) {
56                 PERROR("setuid(0)");
57                 return -1;
58         }
59         return 0;
60 }
61
62 static void compute_new_root(char *base, char **root, const struct passwd *pwd)
63 {
64         int             root_len;
65
66         root_len = 
67                 strlen(base) + strlen("/") +
68                 strlen(pwd->pw_name)      + NULLBYTE_SIZE;
69         (*root) = (char *)malloc(root_len);
70         if ((*root) == NULL) {
71                 PERROR("malloc(%d)", root_len);
72                 exit(1);
73         }
74     
75         sprintf((*root), "%s/%s", base, pwd->pw_name);
76         (*root)[root_len - 1] = '\0';
77 }
78
79 static int sandbox_chroot(const struct passwd *pwd)
80 {
81         char *sandbox_root = NULL;
82
83         compute_new_root(DEFAULT_VSERVERDIR,&sandbox_root, pwd);
84         if (chroot(sandbox_root) < 0) {
85                 PERROR("chroot(%s)", sandbox_root);
86                 exit(1);
87         }
88         if (chdir("/") < 0) {
89                 PERROR("chdir(/)");
90                 exit(1);
91         }
92         return 0;
93 }
94
95 static int sandbox_processes(xid_t ctx, const char *context, const struct passwd *pwd)
96 {
97 #ifdef CONFIG_VSERVER_LEGACY
98         int     flags;
99
100         flags = 0;
101         flags |= 1; /* VX_INFO_LOCK -- cannot request a new vx_id */
102         /* flags |= 4; VX_INFO_NPROC -- limit number of procs in a context */
103
104         (void) vc_new_s_context(ctx, 0, flags);
105
106         /* use legacy dirty hack for capremove */
107         if (vc_new_s_context(VC_SAMECTX, vc_get_insecurebcaps(), flags) == VC_NOCTX) {
108                 PERROR("vc_new_s_context(%u, 0x%16llx, 0x%08x)",
109                        VC_SAMECTX, vc_get_insecurebcaps(), flags);
110                 exit(1);
111         }
112 #else
113         int  ctx_is_new;
114         struct sliver_resources slr;
115         char hostname[HOST_NAME_MAX+1];
116         pl_get_limits(context,&slr);
117
118         if (gethostname(hostname, sizeof hostname) == -1)
119           {
120             PERROR("gethostname(...)");
121             exit(1);
122           }
123
124         /* check whether the slice has been suspended */
125         if (slr.vs_cpu==0)
126           {
127             fprintf(stderr, "*** %s: %s has zero cpu resources and presumably it has been disabled/suspended ***\n", hostname, context);
128             exit(0);
129           }
130
131         (void) (sandbox_chroot(pwd));
132
133         if ((ctx_is_new = pl_chcontext(ctx, ~vc_get_insecurebcaps(),&slr)) < 0)
134           {
135             PERROR("pl_chcontext(%u)", ctx);
136             exit(1);
137           }
138         if (ctx_is_new)
139           {
140             fprintf(stderr, " *** %s: %s has not been started yet, please check back later ***\n", hostname, context);
141             exit(1);
142           }
143 #endif
144         return 0;
145 }
146
147
148 void runas_slice_user(struct passwd *pwd)
149 {
150         char          *username = pwd->pw_name;
151         char          *home_env, *logname_env, *mail_env, *shell_env, *user_env;
152         int           home_len, logname_len, mail_len, shell_len, user_len;
153         static char   *envp[10];
154
155         if (setgid(pwd->pw_gid) < 0) {
156                 PERROR("setgid(%d)", pwd->pw_gid);
157                 exit(1);
158         }
159
160         if (setuid(pwd->pw_uid) < 0) {
161                 PERROR("setuid(%d)", pwd->pw_uid);
162                 exit(1);
163         }
164
165         if (chdir(pwd->pw_dir) < 0) {
166                 PERROR("chdir(%s)", pwd->pw_dir);
167                 exit(1);
168         }
169
170         home_len    = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
171         logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
172         mail_len    = strlen("MAIL=/var/spool/mail/") + strlen(username) 
173                 + NULLBYTE_SIZE;
174         shell_len   = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
175         user_len    = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
176
177         home_env    = (char *)malloc(home_len);
178         logname_env = (char *)malloc(logname_len);
179         mail_env    = (char *)malloc(mail_len);
180         shell_env   = (char *)malloc(shell_len);
181         user_env    = (char *)malloc(user_len);
182
183         if ((home_env    == NULL)  || 
184             (logname_env == NULL)  ||
185             (mail_env    == NULL)  ||
186             (shell_env   == NULL)  ||
187             (user_env    == NULL)) {
188                 PERROR("malloc");
189                 exit(1);
190         }
191
192         sprintf(home_env, "HOME=%s", pwd->pw_dir);
193         sprintf(logname_env, "LOGNAME=%s", username);
194         sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
195         sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
196         sprintf(user_env, "USER=%s", username);
197     
198         home_env[home_len - 1]       = '\0';
199         logname_env[logname_len - 1] = '\0';
200         mail_env[mail_len - 1]       = '\0';
201         shell_env[shell_len - 1]     = '\0';
202         user_env[user_len - 1]       = '\0';
203
204         envp[0] = home_env;
205         envp[1] = logname_env;
206         envp[2] = mail_env;
207         envp[3] = shell_env;
208         envp[4] = user_env;
209         envp[5] = 0;
210
211         if ((putenv(home_env)    < 0) ||
212             (putenv(logname_env) < 0) ||
213             (putenv(mail_env)    < 0) ||
214             (putenv(shell_env)   < 0) ||
215             (putenv(user_env)    < 0)) {
216                 PERROR("vserver: putenv error ");
217                 exit(1);
218         }
219 }
220
221 void slice_enter(struct passwd *pwd)
222 {
223         if (setuidgid_root() < 0) { /* For chroot, new_s_context */
224                 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
225                 exit(2);
226         }
227
228 #ifdef CONFIG_VSERVER_LEGACY
229         (void) (sandbox_chroot(pwd));
230 #endif
231
232         if (sandbox_processes((xid_t) pwd->pw_uid, pwd->pw_name, pwd) < 0) {
233                 fprintf(stderr, "vsh: Could not change context to %d\n", pwd->pw_uid);
234                 exit(2);
235         }
236 }
237
238 //--------------------------------------------------------------------
239
240 #define DEFAULT_SHELL "/bin/sh"
241
242 /* Exit statuses for programs like 'env' that exec other programs.
243    EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs.  */
244 enum
245 {
246   EXIT_CANNOT_INVOKE = 126,
247   EXIT_ENOENT = 127
248 };
249
250 int main(int argc, char **argv)
251 {
252     struct passwd   pwdd, *result, *prechroot, *postchroot = &pwdd;
253     char            *context, *username, *shell, *pwdBuffer;
254     long            pwdBuffer_len;
255     uid_t           uid;
256     int             index, i;
257
258     if (argv[0][0]=='-') 
259       index = 1;
260     else
261       index = 0;
262
263     uid = getuid();
264     if ((prechroot = getpwuid(uid)) == NULL) {
265       PERROR("getpwuid(%d)", uid);
266       exit(1);
267     }
268
269     context = (char*)strdup(prechroot->pw_name);
270     if (!context) {
271       PERROR("strdup");
272       exit(2);
273     }
274
275     /* enter vserver "context" */
276     slice_enter(prechroot);
277
278     /* Get the /etc/passwd entry for this user, this time inside
279      * the chroot.
280      */
281     username = context;
282
283     pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
284     if (pwdBuffer_len == -1) {
285             PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
286             exit(1);
287     }
288     pwdBuffer = (char*)malloc(pwdBuffer_len);
289     if (pwdBuffer == NULL) {
290             PERROR("malloc(%d)", pwdBuffer_len);
291             exit(1);
292     }
293
294     errno = 0;
295     if ((getpwnam_r(username,postchroot,pwdBuffer,pwdBuffer_len, &result) != 0) ||
296         (errno != 0) || result != postchroot) {
297         PERROR("getpwnam_r(%s)", username);
298         exit(1);
299     }
300
301     /* Now run as username in this context. Note that for PlanetLab's
302        vserver configuration the context name also happens to be the
303        "default" username within the vserver context.
304     */
305     runas_slice_user(postchroot);
306
307     /* Make sure pw->pw_shell is non-NULL.*/
308     if (postchroot->pw_shell == NULL || postchroot->pw_shell[0] == '\0') {
309       postchroot->pw_shell = (char *) DEFAULT_SHELL;
310     }
311
312     shell = (char *)strdup(postchroot->pw_shell);
313     if (!shell) {
314       PERROR("strdup");
315       exit(2);
316     }
317
318     /* Check whether 'su' or 'sshd' invoked us as a login shell or
319        not; did this above when testing argv[0]=='-'.
320     */
321     argv[0] = shell;
322     if (index == 1) {
323       char **args;
324       args = (char**)malloc(sizeof(char*)*(argc+2));
325       if (!args) {
326         PERROR("malloc(%d)", sizeof(char*)*(argc+2));
327         exit(1);
328       }
329       args[0] = argv[0];
330       args[1] = "-l";
331       for(i=1;i<argc+1;i++) {
332         args[i+1] = argv[i];
333       }
334       argv = args;
335     }
336     (void) execvp(shell,argv);
337     {
338       int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
339       exit (exit_status);
340     }
341
342     return 0; /* shutup compiler */
343 }