GCC Code Coverage Report


Directory: cvmfs/
File: cvmfs/authz/authz_fetch.cc
Date: 2026-04-12 02:41:08
Exec Total Coverage
Lines: 275 336 81.8%
Branches: 225 457 49.2%

Line Branch Exec Source
1 /**
2 * This file is part of the CernVM File System.
3 */
4
5 #define __STDC_FORMAT_MACROS
6 #include "authz_fetch.h"
7
8 #include <errno.h>
9 #include <signal.h>
10 #include <sys/wait.h>
11 #include <syslog.h>
12 #include <unistd.h>
13
14 #include <algorithm>
15 #include <cassert>
16 #include <cstring>
17 #include <vector>
18
19 #include "monitor.h"
20 #include "options.h"
21 #include "sanitizer.h"
22 #include "util/capabilities.h"
23 #include "util/concurrency.h"
24 #include "util/logging.h"
25 #include "util/platform.h"
26 #include "util/pointer.h"
27 #include "util/posix.h"
28 #include "util/smalloc.h"
29 #include "util/string.h"
30
31 using namespace std; // NOLINT
32
33
34 const int AuthzExternalFetcher::kMinTtl = 0;
35 const uint32_t AuthzExternalFetcher::kProtocolVersion = 1;
36
37
38 1918 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
39 const string &progname,
40 const string &search_path,
41 1918 OptionsManager *options_manager)
42 1918 : fqrn_(fqrn)
43
1/2
✓ Branch 1 taken 1918 times.
✗ Branch 2 not taken.
1918 , progname_(progname)
44
1/2
✓ Branch 1 taken 1918 times.
✗ Branch 2 not taken.
1918 , search_path_(search_path)
45 1918 , fd_send_(-1)
46 1918 , fd_recv_(-1)
47 1918 , pid_(-1)
48 1918 , fail_state_(false)
49 1918 , options_manager_(options_manager)
50
1/2
✓ Branch 3 taken 1918 times.
✗ Branch 4 not taken.
1918 , next_start_(-1) {
51 1918 InitLock();
52 1918 }
53
54 130 AuthzExternalFetcher::AuthzExternalFetcher(const string &fqrn,
55 int fd_send,
56 130 int fd_recv)
57 130 : fqrn_(fqrn)
58 130 , fd_send_(fd_send)
59 130 , fd_recv_(fd_recv)
60 130 , pid_(-1)
61 130 , fail_state_(false)
62 130 , options_manager_(NULL)
63
1/2
✓ Branch 3 taken 130 times.
✗ Branch 4 not taken.
130 , next_start_(-1) {
64 130 InitLock();
65 130 }
66
67
68 8192 AuthzExternalFetcher::~AuthzExternalFetcher() {
69 4096 const int retval = pthread_mutex_destroy(&lock_);
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2048 times.
4096 assert(retval == 0);
71
72 // Allow helper to gracefully terminate
73
3/4
✓ Branch 0 taken 104 times.
✓ Branch 1 taken 1944 times.
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
4096 if ((fd_send_ >= 0) && !fail_state_) {
74 208 LogCvmfs(kLogAuthz, kLogDebug, "shutting down authz helper");
75 624 Send(string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
76 832 + StringifyInt(kAuthzMsgQuit) + "," + "\"revision\":0}}");
77 }
78
79 4096 ReapHelper();
80 8192 }
81
82 2594 void AuthzExternalFetcher::ReapHelper() {
83 // If we are reaping the helper, we don't try to shut it down again.
84
85
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 2386 times.
2594 if (fd_send_ >= 0)
86 208 close(fd_send_);
87 2594 fd_send_ = -1;
88
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 2386 times.
2594 if (fd_recv_ >= 0)
89 208 close(fd_recv_);
90 2594 fd_recv_ = -1;
91
92
2/2
✓ Branch 0 taken 78 times.
✓ Branch 1 taken 2516 times.
2594 if (pid_ > 0) {
93 int retval;
94 78 const uint64_t now = platform_monotonic_time();
95 int statloc;
96 do {
97
1/2
✓ Branch 1 taken 303464148 times.
✗ Branch 2 not taken.
303464148 retval = waitpid(pid_, &statloc, WNOHANG);
98
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 303464122 times.
303464148 if (platform_monotonic_time() > (now + kChildTimeout)) {
99
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 LogCvmfs(kLogAuthz, kLogSyslogWarn | kLogDebug,
100 "authz helper %s unresponsive, killing", progname_.c_str());
101 26 retval = kill(pid_, SIGKILL);
102
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (retval == 0) {
103 // Pick up client return status
104
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 (void)waitpid(pid_, &statloc, 0);
105 } else {
106 // Process might have been terminated just before the kill() call
107 (void)waitpid(pid_, &statloc, WNOHANG);
108 }
109 26 break;
110 }
111
2/2
✓ Branch 0 taken 303464070 times.
✓ Branch 1 taken 52 times.
303464122 } while (retval == 0);
112 78 pid_ = -1;
113 }
114 2594 }
115
116
117 546 void AuthzExternalFetcher::EnterFailState() {
118 546 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
119 "authz helper %s enters fail state, no more authorization",
120 progname_.c_str());
121
122 546 ReapHelper();
123 546 next_start_ = platform_monotonic_time() + kChildTimeout;
124 546 fail_state_ = true;
125 546 }
126
127
128 /**
129 * Uses execve to start progname_. The started program has stdin and stdout
130 * connected to fd_send_ and fd_recv_ and the CVMFS_... environment variables
131 * set. Special care must be taken when we call fork here in an unknown state
132 * of the client. Therefore we can't use ManagedExec (we can't use malloc).
133 *
134 * A failed execve is not caught by this routine. It will be caught in the
135 * next step, when mother and child start talking.
136 */
137 104 void AuthzExternalFetcher::ExecHelper() {
138 int pipe_send[2];
139 int pipe_recv[2];
140
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 MakePipe(pipe_send);
141
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 MakePipe(pipe_recv);
142 104 char *argv0 = strdupa(progname_.c_str());
143 104 char *argv[] = {argv0, NULL};
144
145 104 const bool strip_prefix = true;
146 104 vector<string> authz_env = options_manager_->GetEnvironmentSubset(
147
2/4
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 104 times.
✗ Branch 6 not taken.
208 "CVMFS_AUTHZ_", strip_prefix);
148 104 vector<char *> envp;
149
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 104 times.
104 for (unsigned i = 0; i < authz_env.size(); ++i)
150 envp.push_back(strdupa(authz_env[i].c_str()));
151
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 envp.push_back(strdupa("CVMFS_AUTHZ_HELPER=yes"));
152
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 envp.push_back(NULL);
153
154 #ifdef __APPLE__
155 int max_fd = sysconf(_SC_OPEN_MAX);
156 assert(max_fd > 0);
157 #else
158 104 std::vector<int> open_fds;
159
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 DIR *dirp = opendir("/proc/self/fd");
160
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(dirp);
161 platform_dirent64 *dirent;
162
3/4
✓ Branch 1 taken 3432 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 3328 times.
✓ Branch 4 taken 104 times.
3432 while ((dirent = platform_readdir(dirp))) {
163
1/2
✓ Branch 2 taken 3328 times.
✗ Branch 3 not taken.
3328 const std::string name(dirent->d_name);
164 uint64_t name_uint64;
165 // Make sure the dir name is digits only (skips ".", ".." and similar).
166
3/4
✓ Branch 1 taken 3328 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 208 times.
✓ Branch 4 taken 3120 times.
3328 if (!String2Uint64Parse(name, &name_uint64))
167 208 continue;
168
2/2
✓ Branch 0 taken 208 times.
✓ Branch 1 taken 2912 times.
3120 if (name_uint64 < 2)
169 208 continue;
170
1/2
✓ Branch 1 taken 2912 times.
✗ Branch 2 not taken.
2912 open_fds.push_back(static_cast<int>(name_uint64));
171
2/2
✓ Branch 1 taken 2912 times.
✓ Branch 2 taken 416 times.
3328 }
172
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 closedir(dirp);
173 #endif
174
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 LogCvmfs(kLogAuthz, kLogDebug | kLogSyslog, "starting authz helper %s",
175 argv0);
176
177 104 const pid_t pid = fork();
178
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 104 times.
107 if (pid == 0) {
179 // Child process, close file descriptors and run the helper
180 3 int retval = dup2(pipe_send[0], 0);
181
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 0);
182 3 retval = dup2(pipe_recv[1], 1);
183
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
3 assert(retval == 1);
184 #ifdef __APPLE__
185 for (int fd = 2; fd < max_fd; fd++)
186 close(fd);
187 #else
188
2/2
✓ Branch 1 taken 84 times.
✓ Branch 2 taken 3 times.
87 for (unsigned i = 0; i < open_fds.size(); ++i)
189
1/2
✓ Branch 2 taken 84 times.
✗ Branch 3 not taken.
84 close(open_fds[i]);
190 #endif
191
192
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 3 times.
42 for (size_t i = 0; i < sizeof(Watchdog::g_suppressed_signals) / sizeof(int);
193 i++) {
194 struct sigaction signal_handler;
195 39 signal_handler.sa_handler = SIG_DFL;
196 39 sigaction(Watchdog::g_suppressed_signals[i], &signal_handler, NULL);
197 }
198
199
4/8
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✓ Branch 7 taken 3 times.
✗ Branch 8 not taken.
✓ Branch 9 taken 3 times.
3 if ((getuid() != 0) && SetuidCapabilityPermitted()) {
200 // The helper expects traditional real uid root credentials
201 const uid_t uid = geteuid();
202 const gid_t gid = getegid();
203 if (!SwitchCredentials(0, 0, false) ||
204 !SwitchCredentials(uid, gid, true)) {
205 syslog(LOG_USER | LOG_ERR,
206 "failed to switch back to real uid 0 for authz helper");
207 _exit(1);
208 }
209 }
210
211 3 execve(argv0, argv, &envp[0]);
212 3 syslog(LOG_USER | LOG_ERR, "failed to start authz helper %s (%d)", argv0,
213
0/2
✗ Branch 1 not taken.
✗ Branch 2 not taken.
3 errno);
214 _exit(1);
215 }
216
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 assert(pid > 0);
217
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 close(pipe_send[0]);
218
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 close(pipe_recv[1]);
219
220 // Don't receive a signal if the helper terminates
221 104 signal(SIGPIPE, SIG_IGN);
222 104 pid_ = pid;
223 104 fd_send_ = pipe_send[1];
224 104 fd_recv_ = pipe_recv[0];
225 104 }
226
227
228 AuthzStatus AuthzExternalFetcher::CheckHelper(
229 const std::string &membership)
230 {
231 if ((fd_send_ < 0) && (membership != "")) {
232 if (progname_.empty())
233 progname_ = FindHelper(membership);
234 ExecHelper();
235 if (!Handshake())
236 return kAuthzNoHelper;
237 }
238 return kAuthzOk;
239 }
240
241
242 26 AuthzStatus AuthzExternalFetcher::Fetch(
243 const QueryInfo &query_info,
244 AuthzToken *authz_token,
245 unsigned *ttl)
246 {
247 26 *ttl = kDefaultTtl;
248
249 26 const MutexLockGuard lock_guard(lock_);
250
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (fail_state_) {
251 const uint64_t now = platform_monotonic_time();
252 if (now > next_start_) {
253 fail_state_ = false;
254 } else {
255 return kAuthzNoHelper;
256 }
257 }
258
259 bool retval;
260
261
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (fd_send_ < 0) {
262 return kAuthzNoHelper;
263 }
264
2/4
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 assert((fd_send_ >= 0) && (fd_recv_ >= 0));
265
266 26 string authz_schema;
267 26 string pure_membership;
268
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 StripAuthzSchema(query_info.membership, &authz_schema, &pure_membership);
269
2/4
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 26 times.
✗ Branch 6 not taken.
52 string json_msg = string("{\"cvmfs_authz_v1\":{") + "\"msgid\":"
270
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
104 + StringifyInt(kAuthzMsgVerify) + "," + "\"revision\":0,"
271
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
104 + "\"uid\":" + StringifyInt(query_info.uid) + ","
272
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
104 + "\"gid\":" + StringifyInt(query_info.gid) + ","
273
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
104 + "\"pid\":" + StringifyInt(query_info.pid) + ","
274
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 26 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 26 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 26 times.
✗ Branch 11 not taken.
78 + "\"membership\":\"" + Base64(pure_membership) + "\"}}";
275
4/8
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 26 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 26 times.
✗ Branch 9 not taken.
26 retval = Send(json_msg) && Recv(&json_msg);
276
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!retval)
277 return kAuthzNoHelper;
278 26 AuthzExternalMsg binary_msg;
279
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 retval = ParseMsg(json_msg, kAuthzMsgPermit, &binary_msg);
280
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!retval)
281 return kAuthzNoHelper;
282
283 26 *ttl = binary_msg.permit.ttl;
284
1/2
✓ Branch 0 taken 26 times.
✗ Branch 1 not taken.
26 if (binary_msg.permit.status == kAuthzOk) {
285 26 *authz_token = binary_msg.permit.token;
286 26 LogCvmfs(kLogAuthz, kLogDebug, "got token of type %d and size %u",
287
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 binary_msg.permit.token.type, binary_msg.permit.token.size);
288 }
289
290 26 return binary_msg.permit.status;
291 26 }
292
293
294 string AuthzExternalFetcher::FindHelper(const string &membership) {
295 string authz_schema;
296 string pure_membership;
297 StripAuthzSchema(membership, &authz_schema, &pure_membership);
298 const sanitizer::AuthzSchemaSanitizer sanitizer;
299 if (!sanitizer.IsValid(authz_schema)) {
300 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "invalid authz schema: %s",
301 authz_schema.c_str());
302 return "";
303 }
304
305 string exe_path = search_path_ + "/cvmfs_" + authz_schema + "_helper";
306 if (!FileExists(exe_path)) {
307 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug, "authz helper %s missing",
308 exe_path.c_str());
309 }
310 return exe_path;
311 }
312
313
314 /**
315 * Establish communication link with a forked authz helper.
316 */
317 52 bool AuthzExternalFetcher::Handshake() {
318
1/3
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
52 const string debug_log = GetLogDebugFile();
319 52 string json_debug_log;
320
2/4
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 52 times.
52 if (debug_log != "")
321 json_debug_log = ",\"debug_log\":\"" + debug_log + "\"";
322
2/4
✓ Branch 2 taken 52 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 52 times.
✗ Branch 6 not taken.
104 string json_msg = string("{") + "\"cvmfs_authz_v1\":{"
323
4/8
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 52 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 52 times.
✗ Branch 11 not taken.
156 + "\"msgid\":" + StringifyInt(0) + "," + "\"revision\":0,"
324
5/10
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 52 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 52 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 52 times.
✗ Branch 14 not taken.
156 + "\"fqrn\":\"" + fqrn_ + "\"," + "\"syslog_facility\":"
325
4/8
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 52 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 52 times.
✗ Branch 11 not taken.
208 + StringifyInt(GetLogSyslogFacility()) + ","
326
4/8
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 52 times.
✗ Branch 8 not taken.
✓ Branch 10 taken 52 times.
✗ Branch 11 not taken.
156 + "\"syslog_level\":" + StringifyInt(GetLogSyslogLevel())
327
2/4
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
104 + json_debug_log + "}}";
328
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 bool retval = Send(json_msg);
329
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 26 times.
52 if (!retval)
330 26 return false;
331
332
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 retval = Recv(&json_msg);
333
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!retval)
334 return false;
335 26 AuthzExternalMsg binary_msg;
336
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 retval = ParseMsg(json_msg, kAuthzMsgReady, &binary_msg);
337
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if (!retval)
338 return false;
339
340 26 return true;
341 52 }
342
343
344 2048 void AuthzExternalFetcher::InitLock() {
345 2048 const int retval = pthread_mutex_init(&lock_, NULL);
346
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2048 times.
2048 assert(retval == 0);
347 2048 }
348
349
350 260 bool AuthzExternalFetcher::Send(const string &msg) {
351 // Line format: 4 byte protocol version, 4 byte length, message
352 struct {
353 uint32_t version;
354 uint32_t length;
355 } header;
356 260 header.version = kProtocolVersion;
357 260 header.length = msg.length();
358 260 const unsigned raw_length = sizeof(header) + msg.length();
359 unsigned char *raw_msg = reinterpret_cast<unsigned char *>(
360 260 alloca(raw_length));
361 260 memcpy(raw_msg, &header, sizeof(header));
362 260 memcpy(raw_msg + sizeof(header), msg.data(), header.length);
363
364
1/2
✓ Branch 1 taken 260 times.
✗ Branch 2 not taken.
260 const bool retval = SafeWrite(fd_send_, raw_msg, raw_length);
365
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 208 times.
260 if (!retval)
366
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 EnterFailState();
367 260 return retval;
368 }
369
370
371 /**
372 * We want to see valid JSON in the form
373 * { "cvmfs_authz_v1" : {
374 * "msgid":
375 * "revision":
376 * ...
377 * }
378 * ...
379 * }
380 *
381 * The contents of "cvmfs_authz_v1" depends on the msgid. Additional fields
382 * are ignored. The protocol revision should indicate changes in the fields.
383 */
384 494 bool AuthzExternalFetcher::ParseMsg(const std::string &json_msg,
385 const AuthzExternalMsgIds expected_msgid,
386 AuthzExternalMsg *binary_msg) {
387
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 494 times.
494 assert(binary_msg != NULL);
388
389
2/4
✓ Branch 1 taken 494 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 494 times.
✗ Branch 5 not taken.
494 const UniquePtr<JsonDocument> json_document(JsonDocument::Create(json_msg));
390
2/2
✓ Branch 1 taken 52 times.
✓ Branch 2 taken 442 times.
494 if (!json_document.IsValid()) {
391
1/2
✓ Branch 3 taken 52 times.
✗ Branch 4 not taken.
52 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
392 "invalid json from authz helper %s: %s", progname_.c_str(),
393 json_msg.c_str());
394
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 EnterFailState();
395 52 return false;
396 }
397
398
2/4
✓ Branch 2 taken 442 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 442 times.
✗ Branch 8 not taken.
442 const JSON *json_authz = JsonDocument::SearchInObject(
399 json_document->root(), "cvmfs_authz_v1", JSON_OBJECT);
400
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 416 times.
442 if (json_authz == NULL) {
401
1/2
✓ Branch 3 taken 26 times.
✗ Branch 4 not taken.
26 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
402 "\"cvmfs_authz_v1\" not found in json from authz helper %s: %s",
403 progname_.c_str(), json_msg.c_str());
404
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 EnterFailState();
405 26 return false;
406 }
407
408
1/2
✓ Branch 1 taken 416 times.
✗ Branch 2 not taken.
416 if (!ParseMsgId(json_authz, binary_msg)
409
6/6
✓ Branch 0 taken 312 times.
✓ Branch 1 taken 104 times.
✓ Branch 2 taken 52 times.
✓ Branch 3 taken 260 times.
✓ Branch 4 taken 156 times.
✓ Branch 5 taken 260 times.
416 || (binary_msg->msgid != expected_msgid)) {
410
1/2
✓ Branch 1 taken 156 times.
✗ Branch 2 not taken.
156 EnterFailState();
411 156 return false;
412 }
413
3/4
✓ Branch 1 taken 260 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 234 times.
260 if (!ParseRevision(json_authz, binary_msg)) {
414
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 EnterFailState();
415 26 return false;
416 }
417
2/2
✓ Branch 0 taken 156 times.
✓ Branch 1 taken 78 times.
234 if (binary_msg->msgid == kAuthzMsgPermit) {
418
3/4
✓ Branch 1 taken 156 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 130 times.
156 if (!ParsePermit(json_authz, binary_msg)) {
419
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 EnterFailState();
420 26 return false;
421 }
422 }
423 208 return true;
424 494 }
425
426
427 416 bool AuthzExternalFetcher::ParseMsgId(const JSON *json_authz,
428 AuthzExternalMsg *binary_msg) {
429
2/4
✓ Branch 2 taken 416 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 416 times.
✗ Branch 6 not taken.
416 const JSON *json_msgid = JsonDocument::SearchInObject(json_authz, "msgid",
430 JSON_INT);
431
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 364 times.
416 if (json_msgid == NULL) {
432 52 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
433 "\"msgid\" not found in json from authz helper %s",
434 progname_.c_str());
435 52 EnterFailState();
436 52 return false;
437 }
438
439 364 if ((json_msgid->get<int>() < 0)
440
6/6
✓ Branch 0 taken 338 times.
✓ Branch 1 taken 26 times.
✓ Branch 3 taken 26 times.
✓ Branch 4 taken 312 times.
✓ Branch 5 taken 52 times.
✓ Branch 6 taken 312 times.
364 || (json_msgid->get<int>() >= kAuthzMsgInvalid)) {
441 52 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
442 "invalid \"msgid\" in json from authz helper %s: %d",
443 progname_.c_str(), json_msgid->get<int>());
444 52 EnterFailState();
445 52 return false;
446 }
447
448 312 binary_msg->msgid = static_cast<AuthzExternalMsgIds>(json_msgid->get<int>());
449 312 return true;
450 }
451
452
453 /**
454 * A permit must contain the authorization status. Optionally it can come with
455 * a "time to live" of the answer and a token (e.g. X.509 proxy certificate).
456 */
457 156 bool AuthzExternalFetcher::ParsePermit(const JSON *json_authz,
458 AuthzExternalMsg *binary_msg) {
459
2/4
✓ Branch 2 taken 156 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 156 times.
✗ Branch 6 not taken.
156 const JSON *json_status = JsonDocument::SearchInObject(json_authz, "status",
460 JSON_INT);
461
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 130 times.
156 if (json_status == NULL) {
462 26 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
463 "\"status\" not found in json from authz helper %s",
464 progname_.c_str());
465 26 EnterFailState();
466 26 return false;
467 }
468 130 if ((json_status->get<int>() < 0)
469
3/6
✓ Branch 0 taken 130 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 130 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 130 times.
130 || (json_status->get<int>() > kAuthzUnknown)) {
470 binary_msg->permit.status = kAuthzUnknown;
471 } else {
472 130 binary_msg->permit.status = static_cast<AuthzStatus>(
473 130 json_status->get<int>());
474 }
475
476
2/4
✓ Branch 2 taken 130 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 130 times.
✗ Branch 6 not taken.
130 const JSON *json_ttl = JsonDocument::SearchInObject(json_authz, "ttl",
477 JSON_INT);
478
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 104 times.
130 if (json_ttl == NULL) {
479 26 LogCvmfs(kLogAuthz, kLogDebug, "no ttl, using default");
480 26 binary_msg->permit.ttl = kDefaultTtl;
481 } else {
482 104 binary_msg->permit.ttl = std::max(kMinTtl, json_ttl->get<int>());
483 }
484
485
2/4
✓ Branch 2 taken 130 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 130 times.
✗ Branch 6 not taken.
130 const JSON *json_token = JsonDocument::SearchInObject(
486 json_authz, "x509_proxy", JSON_STRING);
487
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 78 times.
130 if (json_token != NULL) {
488 52 binary_msg->permit.token.type = kTokenX509;
489 52 string token_binary;
490
2/4
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
52 const bool valid_base64 = Debase64(json_token->get<string>(),
491 &token_binary);
492
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 if (!valid_base64) {
493 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
494 "invalid Base64 in 'x509_proxy' from authz helper %s",
495 progname_.c_str());
496 EnterFailState();
497 return false;
498 }
499 52 const unsigned size = token_binary.size();
500 52 binary_msg->permit.token.size = size;
501
1/2
✓ Branch 0 taken 52 times.
✗ Branch 1 not taken.
52 if (size > 0) {
502 // The token is passed to the AuthzSessionManager, which takes care of
503 // freeing the memory
504 52 binary_msg->permit.token.data = smalloc(size);
505 52 memcpy(binary_msg->permit.token.data, token_binary.data(), size);
506 }
507
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 }
508
509
2/4
✓ Branch 2 taken 130 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 130 times.
✗ Branch 6 not taken.
130 json_token = JsonDocument::SearchInObject(json_authz, "bearer_token",
510 JSON_STRING);
511
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 130 times.
130 if (json_token != NULL) {
512 binary_msg->permit.token.type = kTokenBearer;
513 const unsigned size = json_token->get<string>().size();
514 binary_msg->permit.token.size = size;
515 if (size > 0) {
516 // The token is passed to the AuthzSessionManager, which takes care of
517 // freeing the memory
518 binary_msg->permit.token.data = smalloc(size);
519 memcpy(binary_msg->permit.token.data, json_token->get<string>().data(),
520 size);
521
522 LogCvmfs(kLogAuthz, kLogDebug,
523 "Got a bearer_token from authz_helper. "
524 "Setting token type to kTokenBearer");
525 } else {
526 // We got a bearer_token, but a size 0 (or negative?) string
527 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
528 "bearer_token was in returned JSON from Authz helper,"
529 " but of size 0 from authz helper %s",
530 progname_.c_str());
531 }
532 }
533
534
2/2
✓ Branch 0 taken 52 times.
✓ Branch 1 taken 78 times.
130 if (binary_msg->permit.token.type == kTokenUnknown) {
535 // No auth token returned, so authz should do... what exactly?
536 // Log error message
537 52 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
538 "No auth token found in returned JSON from Authz helper %s",
539 progname_.c_str());
540 }
541
542 130 return true;
543 }
544
545
546 260 bool AuthzExternalFetcher::ParseRevision(const JSON *json_authz,
547 AuthzExternalMsg *binary_msg) {
548
2/4
✓ Branch 2 taken 260 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 260 times.
✗ Branch 6 not taken.
260 const JSON *json_revision = JsonDocument::SearchInObject(
549 json_authz, "revision", JSON_INT);
550
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 260 times.
260 if (json_revision == NULL) {
551 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
552 "\"revision\" not found in json from authz helper %s",
553 progname_.c_str());
554 EnterFailState();
555 return false;
556 }
557
558
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 234 times.
260 if (json_revision->get<int>() < 0) {
559 26 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
560 "invalid \"revision\" in json from authz helper %s: %d",
561 progname_.c_str(), json_revision->get<int>());
562 26 EnterFailState();
563 26 return false;
564 }
565
566 234 binary_msg->protocol_revision = json_revision->get<int>();
567 234 return true;
568 }
569
570
571 104 bool AuthzExternalFetcher::Recv(string *msg) {
572 uint32_t version;
573
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 ssize_t retval = SafeRead(fd_recv_, &version, sizeof(version));
574
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 78 times.
104 if (retval != static_cast<int>(sizeof(version))) {
575
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 EnterFailState();
576 26 return false;
577 }
578
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 52 times.
78 if (version != kProtocolVersion) {
579
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
580 "authz helper uses unknown protocol version %u", version);
581
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 EnterFailState();
582 26 return false;
583 }
584
585 uint32_t length;
586
1/2
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
52 retval = SafeRead(fd_recv_, &length, sizeof(length));
587
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 52 times.
52 if (retval != static_cast<int>(sizeof(length))) {
588 EnterFailState();
589 return false;
590 }
591
592 52 msg->clear();
593 char buf[kPageSize];
594 52 unsigned nbytes = 0;
595
2/2
✓ Branch 0 taken 104 times.
✓ Branch 1 taken 52 times.
156 while (nbytes < length) {
596 104 const unsigned remaining = length - nbytes;
597
1/2
✓ Branch 2 taken 104 times.
✗ Branch 3 not taken.
104 retval = SafeRead(fd_recv_, buf, std::min(kPageSize, remaining));
598
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 104 times.
104 if (retval < 0) {
599 LogCvmfs(kLogAuthz, kLogSyslogErr | kLogDebug,
600 "read failure from authz helper %s", progname_.c_str());
601 EnterFailState();
602 return false;
603 }
604 104 nbytes += retval;
605
1/2
✓ Branch 1 taken 104 times.
✗ Branch 2 not taken.
104 msg->append(buf, retval);
606 }
607
608 52 return true;
609 }
610
611
612 26 void AuthzExternalFetcher::StripAuthzSchema(const string &membership,
613 string *authz_schema,
614 string *pure_membership) {
615
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 vector<string> components = SplitString(membership, '%');
616
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 *authz_schema = components[0];
617
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 if (components.size() < 2) {
618
1/2
✓ Branch 2 taken 26 times.
✗ Branch 3 not taken.
26 LogCvmfs(kLogAuthz, kLogDebug, "invalid membership: %s",
619 membership.c_str());
620
1/2
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
26 *pure_membership = "";
621 26 return;
622 }
623
624 components.erase(components.begin());
625 *pure_membership = JoinStrings(components, "%");
626
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 26 times.
26 }
627