getopt32: remove applet_long_options
[oweals/busybox.git] / util-linux / nsenter.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * Mini nsenter implementation for busybox.
4  *
5  * Copyright (C) 2016 by Bartosz Golaszewski <bartekgola@gmail.com>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9
10 //config:config NSENTER
11 //config:       bool "nsenter (8.6 kb)"
12 //config:       default y
13 //config:       select PLATFORM_LINUX
14 //config:       help
15 //config:       Run program with namespaces of other processes.
16
17 //applet:IF_NSENTER(APPLET(nsenter, BB_DIR_USR_BIN, BB_SUID_DROP))
18
19 //kbuild:lib-$(CONFIG_NSENTER) += nsenter.o
20
21 //usage:#define nsenter_trivial_usage
22 //usage:       "[OPTIONS] [PROG [ARGS]]"
23 //usage:#define nsenter_full_usage "\n"
24 //usage:     "\n        -t PID          Target process to get namespaces from"
25 //usage:     "\n        -m[FILE]        Enter mount namespace"
26 //usage:     "\n        -u[FILE]        Enter UTS namespace (hostname etc)"
27 //usage:     "\n        -i[FILE]        Enter System V IPC namespace"
28 //usage:     "\n        -n[FILE]        Enter network namespace"
29 //usage:     "\n        -p[FILE]        Enter pid namespace"
30 //usage:     "\n        -U[FILE]        Enter user namespace"
31 //usage:     "\n        -S UID          Set uid in entered namespace"
32 //usage:     "\n        -G GID          Set gid in entered namespace"
33 //usage:        IF_LONG_OPTS(
34 //usage:     "\n        --preserve-credentials  Don't touch uids or gids"
35 //usage:        )
36 //usage:     "\n        -r[DIR]         Set root directory"
37 //usage:     "\n        -w[DIR]         Set working directory"
38 //usage:     "\n        -F              Don't fork before exec'ing PROG"
39
40 #include <sched.h>
41 #ifndef CLONE_NEWUTS
42 # define CLONE_NEWUTS  0x04000000
43 #endif
44 #ifndef CLONE_NEWIPC
45 # define CLONE_NEWIPC  0x08000000
46 #endif
47 #ifndef CLONE_NEWUSER
48 # define CLONE_NEWUSER 0x10000000
49 #endif
50 #ifndef CLONE_NEWPID
51 # define CLONE_NEWPID  0x20000000
52 #endif
53 #ifndef CLONE_NEWNET
54 # define CLONE_NEWNET  0x40000000
55 #endif
56
57 #include "libbb.h"
58
59 struct namespace_descr {
60         int flag;               /* value passed to setns() */
61         char ns_nsfile8[8];     /* "ns/" + namespace file in process' procfs entry */
62 };
63
64 struct namespace_ctx {
65         char *path;             /* optional path to a custom ns file */
66         int fd;                 /* opened namespace file descriptor */
67 };
68
69 enum {
70         OPT_user        = 1 << 0,
71         OPT_ipc         = 1 << 1,
72         OPT_uts         = 1 << 2,
73         OPT_network     = 1 << 3,
74         OPT_pid         = 1 << 4,
75         OPT_mount       = 1 << 5,
76         OPT_target      = 1 << 6,
77         OPT_setuid      = 1 << 7,
78         OPT_setgid      = 1 << 8,
79         OPT_root        = 1 << 9,
80         OPT_wd          = 1 << 10,
81         OPT_nofork      = 1 << 11,
82         OPT_prescred    = (1 << 12) * ENABLE_LONG_OPTS,
83 };
84 enum {
85         NS_USR_POS = 0,
86         NS_IPC_POS,
87         NS_UTS_POS,
88         NS_NET_POS,
89         NS_PID_POS,
90         NS_MNT_POS,
91         NS_COUNT,
92 };
93 /*
94  * The order is significant in nsenter.
95  * The user namespace comes first, so that it is entered first.
96  * This gives an unprivileged user the potential to enter other namespaces.
97  */
98 static const struct namespace_descr ns_list[] = {
99         { CLONE_NEWUSER, "ns/user", },
100         { CLONE_NEWIPC,  "ns/ipc",  },
101         { CLONE_NEWUTS,  "ns/uts",  },
102         { CLONE_NEWNET,  "ns/net",  },
103         { CLONE_NEWPID,  "ns/pid",  },
104         { CLONE_NEWNS,   "ns/mnt",  },
105 };
106 /*
107  * Upstream nsenter doesn't support the short option for --preserve-credentials
108  */
109 static const char opt_str[] ALIGN1 = "U::i::u::n::p::m::""t+S+G+r::w::F";
110
111 #if ENABLE_LONG_OPTS
112 static const char nsenter_longopts[] ALIGN1 =
113         "user\0"                        Optional_argument       "U"
114         "ipc\0"                         Optional_argument       "i"
115         "uts\0"                         Optional_argument       "u"
116         "network\0"                     Optional_argument       "n"
117         "pid\0"                         Optional_argument       "p"
118         "mount\0"                       Optional_argument       "m"
119         "target\0"                      Required_argument       "t"
120         "setuid\0"                      Required_argument       "S"
121         "setgid\0"                      Required_argument       "G"
122         "root\0"                        Optional_argument       "r"
123         "wd\0"                          Optional_argument       "w"
124         "no-fork\0"                     No_argument             "F"
125         "preserve-credentials\0"        No_argument             "\xff"
126         ;
127 #endif
128
129 /*
130  * Open a file and return the new descriptor. If a full path is provided in
131  * fs_path, then the file to which it points is opened. Otherwise (fd_path is
132  * NULL) the routine builds a path to a procfs file using the following
133  * template: '/proc/<target_pid>/<target_file>'.
134  */
135 static int open_by_path_or_target(const char *path,
136                                   pid_t target_pid, const char *target_file)
137 {
138         char proc_path_buf[sizeof("/proc/%u/1234567890") + sizeof(int)*3];
139
140         if (!path) {
141                 if (target_pid == 0) {
142                         /* Example:
143                          * "nsenter -p PROG" - neither -pFILE nor -tPID given.
144                          */
145                         bb_show_usage();
146                 }
147                 snprintf(proc_path_buf, sizeof(proc_path_buf),
148                          "/proc/%u/%s", (unsigned)target_pid, target_file);
149                 path = proc_path_buf;
150         }
151
152         return xopen(path, O_RDONLY);
153 }
154
155 int nsenter_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
156 int nsenter_main(int argc UNUSED_PARAM, char **argv)
157 {
158         int i;
159         unsigned int opts;
160         const char *root_dir_str = NULL;
161         const char *wd_str = NULL;
162         struct namespace_ctx ns_ctx_list[NS_COUNT];
163         int setgroups_failed;
164         int root_fd, wd_fd;
165         int target_pid = 0;
166         int uid = 0;
167         int gid = 0;
168
169         memset(ns_ctx_list, 0, sizeof(ns_ctx_list));
170
171         opts = getopt32long(argv, opt_str, nsenter_longopts,
172                         &ns_ctx_list[NS_USR_POS].path,
173                         &ns_ctx_list[NS_IPC_POS].path,
174                         &ns_ctx_list[NS_UTS_POS].path,
175                         &ns_ctx_list[NS_NET_POS].path,
176                         &ns_ctx_list[NS_PID_POS].path,
177                         &ns_ctx_list[NS_MNT_POS].path,
178                         &target_pid, &uid, &gid,
179                         &root_dir_str, &wd_str
180         );
181         argv += optind;
182
183         root_fd = wd_fd = -1;
184         if (opts & OPT_root)
185                 root_fd = open_by_path_or_target(root_dir_str,
186                                                  target_pid, "root");
187         if (opts & OPT_wd)
188                 wd_fd = open_by_path_or_target(wd_str, target_pid, "cwd");
189
190         for (i = 0; i < NS_COUNT; i++) {
191                 const struct namespace_descr *ns = &ns_list[i];
192                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
193
194                 ns_ctx->fd = -1;
195                 if (opts & (1 << i))
196                         ns_ctx->fd = open_by_path_or_target(ns_ctx->path,
197                                         target_pid, ns->ns_nsfile8);
198         }
199
200         /*
201          * Entering the user namespace without --preserve-credentials implies
202          * --setuid & --setgid and clearing root's groups.
203          */
204         setgroups_failed = 0;
205         if ((opts & OPT_user) && !(opts & OPT_prescred)) {
206                 opts |= (OPT_setuid | OPT_setgid);
207                 /*
208                  * We call setgroups() before and after setns() and only
209                  * bail-out if it fails twice.
210                  */
211                 setgroups_failed = (setgroups(0, NULL) < 0);
212         }
213
214         for (i = 0; i < NS_COUNT; i++) {
215                 const struct namespace_descr *ns = &ns_list[i];
216                 struct namespace_ctx *ns_ctx = &ns_ctx_list[i];
217
218                 if (ns_ctx->fd < 0)
219                         continue;
220                 if (setns(ns_ctx->fd, ns->flag)) {
221                         bb_perror_msg_and_die(
222                                 "setns(): can't reassociate to namespace '%s'",
223                                 ns->ns_nsfile8 + 3 /* skip over "ns/" */
224                         );
225                 }
226                 close(ns_ctx->fd); /* should close fds, to not confuse exec'ed PROG */
227                 /*ns_ctx->fd = -1;*/
228         }
229
230         if (root_fd >= 0) {
231                 if (wd_fd < 0) {
232                         /*
233                          * Save the current working directory if we're not
234                          * changing it.
235                          */
236                         wd_fd = xopen(".", O_RDONLY);
237                 }
238                 xfchdir(root_fd);
239                 xchroot(".");
240                 close(root_fd);
241                 /*root_fd = -1;*/
242         }
243
244         if (wd_fd >= 0) {
245                 xfchdir(wd_fd);
246                 close(wd_fd);
247                 /*wd_fd = -1;*/
248         }
249
250         /*
251          * Entering the pid namespace implies forking unless it's been
252          * explicitly requested by the user not to.
253          */
254         if (!(opts & OPT_nofork) && (opts & OPT_pid)) {
255                 xvfork_parent_waits_and_exits();
256                 /* Child continues */
257         }
258
259         if (opts & OPT_setgid) {
260                 if (setgroups(0, NULL) < 0 && setgroups_failed)
261                         bb_perror_msg_and_die("setgroups");
262                 xsetgid(gid);
263         }
264         if (opts & OPT_setuid)
265                 xsetuid(uid);
266
267         exec_prog_or_SHELL(argv);
268 }