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