9a235056e3a29ff95e1c561910bf128374408b6f
[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         int unshare_netns;
117
118         pl_get_limits(context,&slr);
119
120         if (gethostname(hostname, sizeof hostname) == -1)
121           {
122             PERROR("gethostname(...)");
123             exit(1);
124           }
125
126         /* check whether the slice has been suspended */
127         if (slr.vs_cpu==0)
128           {
129             fprintf(stderr, "*** %s: %s has zero cpu resources and presumably it has been disabled/suspended ***\n", hostname, context);
130             exit(0);
131           }
132         
133         unshare_netns = pl_unshare_netns(ctx);
134
135         (void) (sandbox_chroot(pwd));
136
137         if ((ctx_is_new = pl_chcontext(ctx, ~vc_get_insecurebcaps(),&slr, 
138                                        unshare_netns)) < 0)
139           {
140             PERROR("pl_chcontext(%u)", ctx);
141             exit(1);
142           }
143         if (ctx_is_new)
144           {
145             fprintf(stderr, " *** %s: %s has not been started yet, please check back later ***\n", hostname, context);
146             exit(1);
147           }
148 #endif
149         return 0;
150 }
151
152
153 void runas_slice_user(struct passwd *pwd)
154 {
155         char          *username = pwd->pw_name;
156         char          *home_env, *logname_env, *mail_env, *shell_env, *user_env;
157         int           home_len, logname_len, mail_len, shell_len, user_len;
158         static char   *envp[10];
159
160         if (setgid(pwd->pw_gid) < 0) {
161                 PERROR("setgid(%d)", pwd->pw_gid);
162                 exit(1);
163         }
164
165         if (setuid(pwd->pw_uid) < 0) {
166                 PERROR("setuid(%d)", pwd->pw_uid);
167                 exit(1);
168         }
169
170         if (chdir(pwd->pw_dir) < 0) {
171                 PERROR("chdir(%s)", pwd->pw_dir);
172                 exit(1);
173         }
174
175         home_len    = strlen("HOME=") + strlen(pwd->pw_dir) + NULLBYTE_SIZE;
176         logname_len = strlen("LOGNAME=") + strlen(username) + NULLBYTE_SIZE;
177         mail_len    = strlen("MAIL=/var/spool/mail/") + strlen(username) 
178                 + NULLBYTE_SIZE;
179         shell_len   = strlen("SHELL=") + strlen(pwd->pw_shell) + NULLBYTE_SIZE;
180         user_len    = strlen("USER=") + strlen(username) + NULLBYTE_SIZE;
181
182         home_env    = (char *)malloc(home_len);
183         logname_env = (char *)malloc(logname_len);
184         mail_env    = (char *)malloc(mail_len);
185         shell_env   = (char *)malloc(shell_len);
186         user_env    = (char *)malloc(user_len);
187
188         if ((home_env    == NULL)  || 
189             (logname_env == NULL)  ||
190             (mail_env    == NULL)  ||
191             (shell_env   == NULL)  ||
192             (user_env    == NULL)) {
193                 PERROR("malloc");
194                 exit(1);
195         }
196
197         sprintf(home_env, "HOME=%s", pwd->pw_dir);
198         sprintf(logname_env, "LOGNAME=%s", username);
199         sprintf(mail_env, "MAIL=/var/spool/mail/%s", username);
200         sprintf(shell_env, "SHELL=%s", pwd->pw_shell);
201         sprintf(user_env, "USER=%s", username);
202     
203         home_env[home_len - 1]       = '\0';
204         logname_env[logname_len - 1] = '\0';
205         mail_env[mail_len - 1]       = '\0';
206         shell_env[shell_len - 1]     = '\0';
207         user_env[user_len - 1]       = '\0';
208
209         envp[0] = home_env;
210         envp[1] = logname_env;
211         envp[2] = mail_env;
212         envp[3] = shell_env;
213         envp[4] = user_env;
214         envp[5] = 0;
215
216         if ((putenv(home_env)    < 0) ||
217             (putenv(logname_env) < 0) ||
218             (putenv(mail_env)    < 0) ||
219             (putenv(shell_env)   < 0) ||
220             (putenv(user_env)    < 0)) {
221                 PERROR("vserver: putenv error ");
222                 exit(1);
223         }
224 }
225
226 void slice_enter(struct passwd *pwd)
227 {
228         if (setuidgid_root() < 0) { /* For chroot, new_s_context */
229                 fprintf(stderr, "vsh: Could not become root, check that SUID flag is set on binary\n");
230                 exit(2);
231         }
232
233 #ifdef CONFIG_VSERVER_LEGACY
234         (void) (sandbox_chroot(pwd));
235 #endif
236
237         if (sandbox_processes((xid_t) pwd->pw_uid, pwd->pw_name, pwd) < 0) {
238                 fprintf(stderr, "vsh: Could not change context to %d\n", pwd->pw_uid);
239                 exit(2);
240         }
241 }
242
243 //--------------------------------------------------------------------
244
245 #define DEFAULT_SHELL "/bin/sh"
246
247 /* Exit statuses for programs like 'env' that exec other programs.
248    EXIT_FAILURE might not be 1, so use EXIT_FAIL in such programs.  */
249 enum
250 {
251   EXIT_CANNOT_INVOKE = 126,
252   EXIT_ENOENT = 127
253 };
254
255 int main(int argc, char **argv)
256 {
257     struct passwd   pwdd, *result, *prechroot, *postchroot = &pwdd;
258     char            *context, *username, *shell, *pwdBuffer;
259     long            pwdBuffer_len;
260     uid_t           uid;
261     int             index, i;
262
263     if (argv[0][0]=='-') 
264       index = 1;
265     else
266       index = 0;
267
268     uid = getuid();
269     if ((prechroot = getpwuid(uid)) == NULL) {
270       PERROR("getpwuid(%d)", uid);
271       exit(1);
272     }
273
274     context = (char*)strdup(prechroot->pw_name);
275     if (!context) {
276       PERROR("strdup");
277       exit(2);
278     }
279
280     /* enter vserver "context" */
281     slice_enter(prechroot);
282
283     /* Get the /etc/passwd entry for this user, this time inside
284      * the chroot.
285      */
286     username = context;
287
288     pwdBuffer_len = sysconf(_SC_GETPW_R_SIZE_MAX);
289     if (pwdBuffer_len == -1) {
290             PERROR("sysconf(_SC_GETPW_R_SIZE_MAX");
291             exit(1);
292     }
293     pwdBuffer = (char*)malloc(pwdBuffer_len);
294     if (pwdBuffer == NULL) {
295             PERROR("malloc(%d)", pwdBuffer_len);
296             exit(1);
297     }
298
299     errno = 0;
300     if ((getpwnam_r(username,postchroot,pwdBuffer,pwdBuffer_len, &result) != 0) ||
301         (errno != 0) || result != postchroot) {
302         PERROR("getpwnam_r(%s)", username);
303         exit(1);
304     }
305
306     /* Now run as username in this context. Note that for PlanetLab's
307        vserver configuration the context name also happens to be the
308        "default" username within the vserver context.
309     */
310     runas_slice_user(postchroot);
311
312     /* Make sure pw->pw_shell is non-NULL.*/
313     if (postchroot->pw_shell == NULL || postchroot->pw_shell[0] == '\0') {
314       postchroot->pw_shell = (char *) DEFAULT_SHELL;
315     }
316
317     shell = (char *)strdup(postchroot->pw_shell);
318     if (!shell) {
319       PERROR("strdup");
320       exit(2);
321     }
322
323     /* Check whether 'su' or 'sshd' invoked us as a login shell or
324        not; did this above when testing argv[0]=='-'.
325     */
326     argv[0] = shell;
327     if (index == 1) {
328       char **args;
329       args = (char**)malloc(sizeof(char*)*(argc+2));
330       if (!args) {
331         PERROR("malloc(%d)", sizeof(char*)*(argc+2));
332         exit(1);
333       }
334       args[0] = argv[0];
335       args[1] = "-l";
336       for(i=1;i<argc+1;i++) {
337         args[i+1] = argv[i];
338       }
339       argv = args;
340     }
341     (void) execvp(shell,argv);
342     {
343       int exit_status = (errno == ENOENT ? EXIT_ENOENT : EXIT_CANNOT_INVOKE);
344       exit (exit_status);
345     }
346
347     return 0; /* shutup compiler */
348 }