1/*
2 * daemon.c
3 *
4 * Copyright (C) 2008-2017 Aerospike, Inc.
5 *
6 * Portions may be licensed to Aerospike, Inc. under one or more contributor
7 * license agreements.
8 *
9 * This program is free software: you can redistribute it and/or modify it under
10 * the terms of the GNU Affero General Public License as published by the Free
11 * Software Foundation, either version 3 of the License, or (at your option) any
12 * later version.
13 *
14 * This program is distributed in the hope that it will be useful, but WITHOUT
15 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17 * details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see http://www.gnu.org/licenses/
21 */
22
23/*
24 * process utilities
25 */
26
27#include "daemon.h"
28
29#include <errno.h>
30#include <fcntl.h>
31#include <grp.h>
32#include <stdbool.h>
33#include <stdint.h>
34#include <stdio.h>
35#include <stdlib.h>
36#include <unistd.h>
37#include <linux/capability.h>
38#include <sys/prctl.h>
39#include <sys/stat.h>
40#include <sys/types.h>
41
42#include "fault.h"
43
44extern int capset(cap_user_header_t header, cap_user_data_t data);
45extern int capget(cap_user_header_t header, cap_user_data_t data);
46
47
48static uint32_t g_startup_caps[2] = { 0, 0 }; // held during startup
49static uint32_t g_runtime_caps[2] = { 0, 0 }; // held forever
50static bool g_kept_caps = false;
51
52
53void
54cf_process_privsep(uid_t uid, gid_t gid)
55{
56 uid_t curr_uid = geteuid();
57 gid_t curr_gid = getegid();
58
59 // In case people use the setuid/setgid bit on the asd executable.
60 CF_NEVER_FAILS(setuid(curr_uid));
61 CF_NEVER_FAILS(setgid(curr_gid));
62
63 // To see this log message, change NO_SINKS_LIMIT in fault.c.
64 cf_info(AS_AS, "user %d, group %d -> user %d, group %d", curr_uid, curr_gid,
65 uid, gid);
66
67 // Not started as root: may switch neither user nor group.
68
69 if (curr_uid != 0) {
70 if (uid != (uid_t)-1 && uid != curr_uid) {
71 cf_crash_nostack(CF_MISC,
72 "insufficient privileges to switch user to %d", uid);
73 }
74
75 if (gid != (gid_t)-1 && gid != curr_gid) {
76 cf_crash_nostack(CF_MISC,
77 "insufficient privileges to switch group to %d", gid);
78 }
79
80 return;
81 }
82
83 // Started as root and staying root: may switch group.
84
85 if (uid == (uid_t)-1 || uid == 0) {
86 if (gid == (gid_t)-1) {
87 return;
88 }
89
90 if (setgroups(0, (const gid_t *)0) < 0) {
91 cf_crash(CF_MISC, "setgroups: %s", cf_strerror(errno));
92 }
93
94 if (setgid(gid) < 0) {
95 cf_crash(CF_MISC, "setgid: %s", cf_strerror(errno));
96 }
97
98 return;
99 }
100
101 // Started as root and not staying root: switch user (group), keep caps.
102
103 uint32_t caps[2] = {
104 g_startup_caps[0] | g_runtime_caps[0],
105 g_startup_caps[1] | g_runtime_caps[1]
106 };
107
108 // If required, make capabilities survive the UID/GID switch.
109
110 if (caps[0] != 0 || caps[1] != 0) {
111 if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) {
112 cf_crash(CF_MISC, "prctl: %s", cf_strerror(errno));
113 }
114
115 g_kept_caps = true;
116 }
117
118 if (gid != (gid_t)-1) {
119 if (setgroups(0, (const gid_t *)0) < 0) {
120 cf_crash(CF_MISC, "setgroups: %s", cf_strerror(errno));
121 }
122
123 if (setgid(gid) < 0) {
124 cf_crash(CF_MISC, "setgid: %s", cf_strerror(errno));
125 }
126 }
127
128 if (setuid(uid) < 0) {
129 cf_crash(CF_MISC, "setuid: %s", cf_strerror(errno));
130 }
131
132 // If we made the capabilities survive, reduce them to the desired set.
133
134 if (! g_kept_caps) {
135 return;
136 }
137
138 struct __user_cap_header_struct cap_head = {
139 .version = _LINUX_CAPABILITY_VERSION_3
140 };
141
142 struct __user_cap_data_struct cap_data[2] = {
143 { .permitted = caps[0], .effective = caps[0] },
144 { .permitted = caps[1], .effective = caps[1] }
145 };
146
147 if (capset(&cap_head, cap_data) < 0) {
148 cf_crash(CF_MISC, "capset: %s", cf_strerror(errno));
149 }
150}
151
152
153void
154cf_process_add_startup_cap(int cap)
155{
156 g_startup_caps[cap >> 5] = 1 << (cap & 0x1f);
157}
158
159
160void
161cf_process_add_runtime_cap(int cap)
162{
163 g_runtime_caps[cap >> 5] = 1 << (cap & 0x1f);
164}
165
166
167void
168cf_process_drop_startup_caps(void)
169{
170 if (! g_kept_caps) {
171 return;
172 }
173
174 struct __user_cap_header_struct cap_head = {
175 .version = _LINUX_CAPABILITY_VERSION_3
176 };
177
178 struct __user_cap_data_struct cap_data[2] = {
179 { .permitted = g_runtime_caps[0] },
180 { .permitted = g_runtime_caps[1] }
181 };
182
183 if (capset(&cap_head, cap_data) < 0) {
184 cf_crash(CF_MISC, "capset: %s", cf_strerror(errno));
185 }
186}
187
188
189bool
190cf_process_has_cap(int cap)
191{
192 struct __user_cap_header_struct cap_head = {
193 .version = _LINUX_CAPABILITY_VERSION_3
194 };
195
196 struct __user_cap_data_struct cap_data[2];
197
198 if (capget(&cap_head, cap_data) < 0) {
199 cf_crash(CF_MISC, "capget: %s", cf_strerror(errno));
200 }
201
202 uint32_t perm = cap_data[cap >> 5].permitted;
203
204 return (perm & (1 << (cap & 0x1f))) != 0;
205}
206
207
208void
209cf_process_enable_cap(int cap)
210{
211 if (! g_kept_caps) {
212 return;
213 }
214
215 struct __user_cap_header_struct cap_head = {
216 .version = _LINUX_CAPABILITY_VERSION_3
217 };
218
219 struct __user_cap_data_struct cap_data[2];
220
221 if (capget(&cap_head, cap_data) < 0) {
222 cf_crash(CF_MISC, "capget: %s", cf_strerror(errno));
223 }
224
225 cap_data[cap >> 5].effective |= 1 << (cap & 0x1f);
226
227 if (capset(&cap_head, cap_data) < 0) {
228 cf_crash(CF_MISC, "capset: %s", cf_strerror(errno));
229 }
230}
231
232
233void
234cf_process_disable_cap(int cap)
235{
236 if (! g_kept_caps) {
237 return;
238 }
239
240 struct __user_cap_header_struct cap_head = {
241 .version = _LINUX_CAPABILITY_VERSION_3
242 };
243
244 struct __user_cap_data_struct cap_data[2];
245
246 if (capget(&cap_head, cap_data) < 0) {
247 cf_crash(CF_MISC, "capget: %s", cf_strerror(errno));
248 }
249
250 cap_data[cap >> 5].effective &= ~(1 << (cap & 0x1f));
251
252 if (capset(&cap_head, cap_data) < 0) {
253 cf_crash(CF_MISC, "capset: %s", cf_strerror(errno));
254 }
255}
256
257
258// Daemonize the server - fork a new child process and exit the parent process.
259// Close all the file descriptors opened except the ones specified in the
260// fd_ignore_list. Redirect console messages to a file.
261void
262cf_process_daemonize(int *fd_ignore_list, int list_size)
263{
264 int FD, j;
265 char cfile[128];
266 pid_t p;
267
268 // Fork ourselves, then let the parent expire.
269 if (-1 == (p = fork())) {
270 cf_crash(CF_MISC, "couldn't fork: %s", cf_strerror(errno));
271 }
272
273 if (0 != p) {
274 // Prefer _exit() over exit(), as we don't want the parent to
275 // do any cleanups.
276 _exit(0);
277 }
278
279 // Get a new session.
280 if (-1 == setsid()) {
281 cf_crash(CF_MISC, "couldn't set session: %s", cf_strerror(errno));
282 }
283
284 // Drop all the file descriptors except the ones in fd_ignore_list.
285 for (int i = getdtablesize(); i > 2; i--) {
286 for (j = 0; j < list_size; j++) {
287 if (fd_ignore_list[j] == i) {
288 break;
289 }
290 }
291
292 if (j == list_size) {
293 close(i);
294 }
295 }
296
297 // Open a temporary file for console message redirection.
298 snprintf(cfile, 128, "/tmp/aerospike-console.%d", getpid());
299
300 if (-1 == (FD = open(cfile, O_WRONLY|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR))) {
301 cf_crash(CF_MISC, "couldn't open console redirection file %s: %s", cfile, cf_strerror(errno));
302 }
303
304 if (-1 == chmod(cfile, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)) {
305 cf_crash(CF_MISC, "couldn't set mode on console redirection file %s: %s", cfile, cf_strerror(errno));
306 }
307
308 // Redirect stdout, stderr, and stdin to the console file.
309 for (int i = 0; i < 3; i++) {
310 if (-1 == dup2(FD, i)) {
311 cf_crash(CF_MISC, "couldn't duplicate FD: %s", cf_strerror(errno));
312 }
313 }
314}
315