blob: 578e2b116cecc98634da1bb829091040f8d901eb [file] [log] [blame]
Harald Weltec6a86972019-12-16 23:14:45 +01001/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
2 *
3 * All Rights Reserved
4 *
5 * SPDX-License-Identifier: GPL-2.0+
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 */
22
23#include "config.h"
24#ifndef EMBEDDED
25
Harald Weltef3cc7312020-04-17 18:23:52 +020026#define _GNU_SOURCE
Harald Weltec6a86972019-12-16 23:14:45 +010027#include <unistd.h>
28
29#include <errno.h>
30#include <string.h>
31
32#include <stdio.h>
33#include <dirent.h>
34#include <sys/types.h>
Harald Weltef3cc7312020-04-17 18:23:52 +020035#include <pwd.h>
Harald Weltec6a86972019-12-16 23:14:45 +010036
37#include <osmocom/core/logging.h>
38#include <osmocom/core/utils.h>
39#include <osmocom/core/exec.h>
40
41/*! suggested list of environment variables to pass (if they exist) to a sub-process/script */
42const char *osmo_environment_whitelist[] = {
43 "USER", "LOGNAME", "HOME",
44 "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME",
45 "PATH",
46 "PWD",
47 "SHELL",
48 "TERM",
49 "TMPDIR",
50 "LD_LIBRARY_PATH",
51 "LD_PRELOAD",
52 "POSIXLY_CORRECT",
53 "HOSTALIASES",
54 "TZ", "TZDIR",
55 "TERMCAP",
56 "COLUMNS", "LINES",
57 NULL
58};
59
60static bool str_in_list(const char **list, const char *key)
61{
62 const char **ent;
63
64 for (ent = list; *ent; ent++) {
65 if (!strcmp(*ent, key))
66 return true;
67 }
68 return false;
69}
70
71/*! filtered a process environment by whitelist; only copying pointers, no actual strings.
72 *
73 * This function is useful if you'd like to generate an environment to pass exec*e()
74 * functions. It will create a new environment containing only those entries whose
75 * keys (as per environment convention KEY=VALUE) are contained in the whitelist. The
76 * function will not copy the actual strings, but just create a new pointer array, pointing
77 * to the same memory as the input strings.
78 *
79 * Constraints: Keys up to a maximum length of 255 characters are supported.
80 *
81 * \oaram[out] out caller-allocated array of pointers for the generated output
82 * \param[in] out_len size of out (number of pointers)
83 * \param[in] in input environment (NULL-terminated list of pointers like **environ)
84 * \param[in] whitelist whitelist of permitted keys in environment (like **environ)
85 * \returns number of entries filled in 'out'; negtive on error */
86int osmo_environment_filter(char **out, size_t out_len, char **in, const char **whitelist)
87{
88 char tmp[256];
89 char **ent;
90 size_t out_used = 0;
91
92 /* invalid calls */
93 if (!out || out_len == 0 || !whitelist)
94 return -EINVAL;
95
96 /* legal, but unusual: no input to filter should generate empty, terminated out */
97 if (!in) {
98 out[0] = NULL;
99 return 1;
100 }
101
102 /* iterate over input entries */
103 for (ent = in; *ent; ent++) {
104 char *eq = strchr(*ent, '=');
105 unsigned long eq_pos;
106 if (!eq) {
107 /* no '=' in string, skip it */
108 continue;
109 }
110 eq_pos = eq - *ent;
111 if (eq_pos >= ARRAY_SIZE(tmp))
112 continue;
113 strncpy(tmp, *ent, eq_pos);
114 tmp[eq_pos] = '\0';
115 if (str_in_list(whitelist, tmp)) {
116 if (out_used == out_len-1)
117 break;
118 /* append to output */
119 out[out_used++] = *ent;
120 }
121 }
122 OSMO_ASSERT(out_used < out_len);
123 out[out_used++] = NULL;
124 return out_used;
125}
126
127/*! append one environment to another; only copying pointers, not actual strings.
128 *
129 * This function is useful if you'd like to append soem entries to an environment
130 * befoer passing it to exec*e() functions.
131 *
132 * It will append all entries from 'in' to the environment in 'out', as long as
133 * 'out' has space (determined by 'out_len').
134 *
135 * Constraints: If the same key exists in 'out' and 'in', duplicate keys are
136 * generated. It is a simple append, without any duplicate checks.
137 *
138 * \oaram[out] out caller-allocated array of pointers for the generated output
139 * \param[in] out_len size of out (number of pointers)
140 * \param[in] in input environment (NULL-terminated list of pointers like **environ)
141 * \returns number of entries filled in 'out'; negative on error */
142int osmo_environment_append(char **out, size_t out_len, char **in)
143{
144 size_t out_used = 0;
145
146 if (!out || out_len == 0)
147 return -EINVAL;
148
149 /* seek to end of existing output */
150 for (out_used = 0; out[out_used]; out_used++) {}
151
152 if (!in) {
153 if (out_used == 0)
154 out[out_used++] = NULL;
155 return out_used;
156 }
157
158 for (; *in && out_used < out_len-1; in++)
159 out[out_used++] = *in;
160
161 OSMO_ASSERT(out_used < out_len);
162 out[out_used++] = NULL;
163
164 return out_used;
165}
166
167/* Iterate over files in /proc/self/fd and close all above lst_fd_to_keep */
168int osmo_close_all_fds_above(int last_fd_to_keep)
169{
170 struct dirent *ent;
171 DIR *dir;
172 int rc;
173
174 dir = opendir("/proc/self/fd");
175 if (!dir) {
176 LOGP(DLGLOBAL, LOGL_ERROR, "Cannot open /proc/self/fd: %s\n", strerror(errno));
177 return -ENODEV;
178 }
179
180 while ((ent = readdir(dir))) {
181 int fd = atoi(ent->d_name);
182 if (fd <= last_fd_to_keep)
183 continue;
184 if (fd == dirfd(dir))
185 continue;
186 rc = close(fd);
187 if (rc)
188 LOGP(DLGLOBAL, LOGL_ERROR, "Error closing fd=%d: %s\n", fd, strerror(errno));
189 }
190 closedir(dir);
191 return 0;
192}
193
194/* Seems like POSIX has no header file for this, and even glibc + __USE_GNU doesn't help */
195extern char **environ;
196
Harald Weltef3cc7312020-04-17 18:23:52 +0200197/*! call an external shell command as 'user' without waiting for it.
Harald Weltec6a86972019-12-16 23:14:45 +0100198 *
199 * This mimics the behavior of system(3), with the following differences:
200 * - it doesn't wait for completion of the child process
201 * - it closes all non-stdio file descriptors by iterating /proc/self/fd
202 * - it constructs a reduced environment where only whitelisted keys survive
203 * - it (optionally) appends additional variables to the environment
Harald Weltef3cc7312020-04-17 18:23:52 +0200204 * - it (optionally) changes the user ID to that of 'user' (requires execution as root)
Harald Weltec6a86972019-12-16 23:14:45 +0100205 *
206 * \param[in] command the shell command to be executed, see system(3)
207 * \param[in] env_whitelist A white-list of keys for environment variables
208 * \param[in] addl_env any additional environment variables to be appended
Harald Weltef3cc7312020-04-17 18:23:52 +0200209 * \param[in] user name of the user to which we should switch before executing the command
Harald Weltec6a86972019-12-16 23:14:45 +0100210 * \returns PID of generated child process; negative on error
211 */
Harald Weltef3cc7312020-04-17 18:23:52 +0200212int osmo_system_nowait2(const char *command, const char **env_whitelist, char **addl_env, const char *user)
Harald Weltec6a86972019-12-16 23:14:45 +0100213{
Harald Weltef3cc7312020-04-17 18:23:52 +0200214 struct passwd _pw, *pw;
215 int getpw_buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
Harald Weltec6a86972019-12-16 23:14:45 +0100216 int rc;
217
Harald Weltef3cc7312020-04-17 18:23:52 +0200218 if (user) {
219 char buf[getpw_buflen];
220 getpwnam_r(user, &_pw, buf, sizeof(buf), &pw);
221 if (!pw)
222 return -EINVAL;
223 }
224
Harald Weltec6a86972019-12-16 23:14:45 +0100225 rc = fork();
226 if (rc == 0) {
227 /* we are in the child */
228 char *new_env[1024];
229
230 /* close all file descriptors above stdio */
231 osmo_close_all_fds_above(2);
232
Vadim Yanitskiyd419f652020-02-09 04:44:35 +0700233 /* man execle: "an array of pointers *must* be terminated by a null pointer" */
234 new_env[0] = NULL;
235
Harald Weltec6a86972019-12-16 23:14:45 +0100236 /* build the new environment */
Vadim Yanitskiy979c3b72020-02-09 05:06:12 +0700237 if (env_whitelist) {
238 rc = osmo_environment_filter(new_env, ARRAY_SIZE(new_env), environ, env_whitelist);
239 if (rc < 0)
240 return rc;
241 }
242 if (addl_env) {
243 rc = osmo_environment_append(new_env, ARRAY_SIZE(new_env), addl_env);
244 if (rc < 0)
245 return rc;
246 }
Harald Weltec6a86972019-12-16 23:14:45 +0100247
Harald Weltef3cc7312020-04-17 18:23:52 +0200248 /* drop privileges */
249 if (pw) {
250 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) < 0) {
251 perror("setresgid() during privilege drop");
252 exit(1);
253 }
254
255 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0) {
256 perror("setresuid() during privilege drop");
257 exit(1);
258 }
259
260 }
261
Harald Weltec6a86972019-12-16 23:14:45 +0100262 /* if we want to behave like system(3), we must go via the shell */
263 execle("/bin/sh", "sh", "-c", command, (char *) NULL, new_env);
264 /* only reached in case of error */
265 LOGP(DLGLOBAL, LOGL_ERROR, "Error executing command '%s' after fork: %s\n",
266 command, strerror(errno));
267 return -EIO;
268 } else {
269 /* we are in the parent */
270 return rc;
271 }
272}
273
Harald Weltef3cc7312020-04-17 18:23:52 +0200274/*! call an external shell command without waiting for it.
275 *
276 * This mimics the behavior of system(3), with the following differences:
277 * - it doesn't wait for completion of the child process
278 * - it closes all non-stdio file descriptors by iterating /proc/self/fd
279 * - it constructs a reduced environment where only whitelisted keys survive
280 * - it (optionally) appends additional variables to the environment
281 *
282 * \param[in] command the shell command to be executed, see system(3)
283 * \param[in] env_whitelist A white-list of keys for environment variables
284 * \param[in] addl_env any additional environment variables to be appended
285 * \returns PID of generated child process; negative on error
286 */
287int osmo_system_nowait(const char *command, const char **env_whitelist, char **addl_env)
288{
289 return osmo_system_nowait2(command, env_whitelist, addl_env, NULL);
290}
291
292
Harald Weltec6a86972019-12-16 23:14:45 +0100293#endif /* EMBEDDED */