-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathauthpolicy_parser_main.cc
597 lines (520 loc) · 21.2 KB
/
authpolicy_parser_main.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
// Copyright 2016 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Command line tool to parse data. Data is read from stdin as string or
// protobuf and returned through stdout in string or protobuf format. The tool
// is invoked by the authpolicy daemon in a secure sandbox. It is done this way
// since parsing the output is considered insecure.
//
// Usage:
// authpolicy_parser <command> <serialized_debug_flags>
// For a list of commands see constants.h.
// Each command reads additional arguments from stdin. See code for details.
//
// Logs to syslog.
#include <time.h>
#include <string>
#include <vector>
#include <base/at_exit.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
#include <brillo/syslog_logging.h>
#include "authpolicy/authpolicy_flags.h"
#include "authpolicy/constants.h"
#include "authpolicy/log_colors.h"
#include "authpolicy/platform_helper.h"
#include "authpolicy/policy/preg_policy_encoder.h"
#include "authpolicy/proto_bindings/active_directory_info.pb.h"
#include "authpolicy/samba_helper.h"
#include "bindings/authpolicy_containers.pb.h"
#include "bindings/chrome_device_policy.pb.h"
#include "bindings/cloud_policy.pb.h"
namespace em = enterprise_management;
namespace authpolicy {
namespace {
// 'net ads gpo list' tokens.
const char kGpoToken_Separator[] = "---------------------";
const char kGpoToken_Name[] = "name";
const char kGpoToken_Filesyspath[] = "filesyspath";
const char kGpoToken_VersionUser[] = "version_user";
const char kGpoToken_VersionMachine[] = "version_machine";
const char kGpoToken_Options[] = "options";
// 'net ads' tokens.
const char kToken_NoResults[] = "Got 0 replies";
const char kToken_KdcServer[] = "KDC server";
const char kToken_ServerTime[] = "Server time";
const char kToken_DomainController[] = "Domain Controller";
const char kToken_Workgroup[] = "Workgroup";
// Length of the klist date/time format (mm/dd/yy HH:MM:SS).
const int kDateTimeStringLength = 18;
// Various offsets from the beginning of a line of date/time strings in the
// klist output.
const size_t kValidFromOffset = 0;
const size_t kExpiresOffset = 19;
const size_t kRenewUntilOffset = 11;
// String in klist output that prefixes the renewal lifetime.
const char kRenewUntil[] = "renew until ";
// Grace time before printing warnings like "TGT not yet valid?" since it's
// generating a lot of false positives otherwise. The reason could be time
// discrepancies between client and server.
const int kTgtWarningGraceTimeSeconds = 300;
struct GpoEntry {
GpoEntry() { Clear(); }
void Clear() {
name.clear();
filesyspath.clear();
version_user = 0;
version_machine = 0;
gp_flags = kGpFlagInvalid;
}
bool IsValid() const {
return !name.empty() && !filesyspath.empty() &&
!(version_user == 0 && version_machine == 0) &&
gp_flags != kGpFlagInvalid;
}
bool IsEmpty() const {
return name.empty() && filesyspath.empty() && version_user == 0 &&
version_machine == 0 && gp_flags == kGpFlagInvalid;
}
void Log() const {
LOG(INFO) << kColorGpo << " Name: " << name << kColorReset;
LOG(INFO) << kColorGpo << " Version: " << version_user << " (user) "
<< version_machine << " (machine)" << kColorReset;
LOG(INFO) << kColorGpo << " GPFLags: " << gp_flags << kColorReset;
}
std::string name;
std::string filesyspath;
unsigned int version_user;
unsigned int version_machine;
int gp_flags;
};
void PushGpo(const GpoEntry& gpo,
PolicyScope scope,
std::vector<GpoEntry>* gpo_list,
const protos::DebugFlags& flags) {
if (gpo.IsEmpty())
return;
if (!gpo.IsValid() && flags.log_gpo()) {
LOG(INFO) << kColorGpo << "Ignoring invalid GPO" << kColorReset;
gpo.Log();
return;
}
// Filter out GPOs we don't need. If version_user == 0, there's no user
// policy stored in that GPO. Similarly, if version_machine == 0, there's no
// device policy.
const char* filter_reason = nullptr;
switch (scope) {
case PolicyScope::USER:
if (gpo.version_user == 0)
filter_reason = "user version is 0";
else if (gpo.gp_flags & kGpFlagUserDisabled)
filter_reason = "user disabled flag is set";
break;
case PolicyScope::MACHINE:
if (gpo.version_machine == 0)
filter_reason = "machine version is 0";
else if (gpo.gp_flags & kGpFlagMachineDisabled)
filter_reason = "machine disabled flag is set";
break;
}
if (!filter_reason) {
gpo_list->push_back(gpo);
} else if (flags.log_gpo()) {
LOG(INFO) << kColorGpo << "Filtered out GPO (" << filter_reason << ")"
<< kColorReset;
gpo.Log();
}
}
// Prints |str| to stdout for the caller of this tool. Returns an exit code that
// indicates success or failure.
int OutputForCaller(const std::string& str) {
if (!base::WriteFileDescriptor(STDOUT_FILENO, str.c_str(), str.size())) {
LOG(ERROR) << "Failed to write output for caller";
return EXIT_CODE_WRITE_OUTPUT_FAILED;
}
return EXIT_CODE_OK;
}
// Parses the substring starting at offset |offset| of |str| for a date/time
// formatted mm/dd/yy HH:MM:SS. The time is interpreted as local time. Sets
// |time| to the number of seconds in the epoch or 0 on error. Returns true on
// success.
bool ParseTgtDateTime(const std::string& str, size_t offset, time_t* time) {
*time = 0;
if (offset >= str.size())
return false;
std::string datetime = str.substr(offset, kDateTimeStringLength);
if (datetime.size() < kDateTimeStringLength)
return false;
struct tm tm = {};
if (!strptime(datetime.c_str(), "%m/%d/%y %H:%M:%S", &tm))
return false;
// Figure out daylight saving time (strptime doesn't set this).
tm.tm_isdst = -1;
*time = mktime(&tm);
return true;
}
// Parses the output of net ads info into a ServerInfo protobuf and prints
// it to stdout.
int ParseServerInfo(const std::string& net_out) {
std::string kdc_ip, server_time_str;
if (!FindToken(net_out, ':', kToken_KdcServer, &kdc_ip) ||
!FindToken(net_out, ':', kToken_ServerTime, &server_time_str)) {
LOG(ERROR) << "Failed to parse server info";
return EXIT_CODE_FIND_TOKEN_FAILED;
}
// Parse time. The time format is "Thu, 15 Feb 2018 11:21:26 PST".
base::Time server_time;
if (!base::Time::FromString(server_time_str.c_str(), &server_time)) {
LOG(ERROR) << "Failed to parse server time " << server_time_str;
return EXIT_CODE_PARSE_INPUT_FAILED;
}
// Put data into proto.
protos::ServerInfo server_info;
server_info.set_kdc_ip(kdc_ip);
server_info.set_server_time(server_time.ToInternalValue());
std::string server_info_blob;
if (!server_info.SerializeToString(&server_info_blob)) {
LOG(ERROR) << "Failed to convert server info proto to string";
return EXIT_CODE_WRITE_OUTPUT_FAILED;
}
return OutputForCaller(server_info_blob);
}
// Parses the output of net ads search to get the user's account info and prints
// it to stdout. Prints an empty string in case of no search results.
int ParseAccountInfo(const std::string& net_out) {
// Return an empty string, but no error, if no results have been found.
if (base::StartsWith(net_out, kToken_NoResults, base::CompareCase::SENSITIVE))
return OutputForCaller("");
// Parse required attributes.
std::string object_guid;
std::string sam_account_name;
std::string common_name;
if (!FindToken(net_out, ':', kSearchObjectGUID, &object_guid) ||
!FindToken(net_out, ':', kSearchSAMAccountName, &sam_account_name) ||
!FindToken(net_out, ':', kSearchCommonName, &common_name)) {
LOG(ERROR) << "Failed to parse account info";
return EXIT_CODE_FIND_TOKEN_FAILED;
}
// Put data into proto.
ActiveDirectoryAccountInfo account_info;
account_info.set_account_id(object_guid);
account_info.set_sam_account_name(sam_account_name);
account_info.set_common_name(common_name);
// pwdLastSet might be missing, see crbug.com/795758. Handle it gracefully.
std::string pwd_last_set_str;
if (FindToken(net_out, ':', kSearchPwdLastSet, &pwd_last_set_str)) {
uint64_t pwd_last_set;
if (!base::StringToUint64(pwd_last_set_str, &pwd_last_set)) {
LOG(WARNING) << "Failed to convert pwdLastSet string '"
<< pwd_last_set_str << "' to integer";
} else {
account_info.set_pwd_last_set(pwd_last_set);
}
}
// Likewise, handle missing userAccountControl just in case.
std::string user_account_control_str;
if (FindToken(net_out, ':', kSearchUserAccountControl,
&user_account_control_str)) {
uint32_t user_account_control;
if (!base::StringToUint(user_account_control_str, &user_account_control)) {
LOG(WARNING) << "Failed to convert userAccountControl string '"
<< user_account_control_str << "' to integer";
} else {
account_info.set_user_account_control(user_account_control);
}
}
// Attributes 'displayName' and 'givenName' are optional. May be missing for
// accounts like 'Administrator' or for partially set up accounts.
std::string display_name, given_name;
if (FindToken(net_out, ':', kSearchDisplayName, &display_name))
account_info.set_display_name(display_name);
if (FindToken(net_out, ':', kSearchGivenName, &given_name))
account_info.set_given_name(given_name);
std::string account_info_blob;
if (!account_info.SerializeToString(&account_info_blob)) {
LOG(ERROR) << "Failed to convert account info proto to string";
return EXIT_CODE_WRITE_OUTPUT_FAILED;
}
return OutputForCaller(account_info_blob);
}
// Parses the output of a net ads command for '|token| : value'. Prints value to
// stdout.
int ParseSingleToken(const std::string& net_out, const std::string& token) {
std::string value;
if (!FindToken(net_out, ':', token, &value))
return EXIT_CODE_FIND_TOKEN_FAILED;
return OutputForCaller(value);
}
// Parses the output of net ads gpo list to get the list of GPOs. Prints out a
// serialized GpoList blob to stdout.
int ParseGpoList(const std::string& net_out,
PolicyScope scope,
const protos::DebugFlags& flags) {
// Parse net output.
GpoEntry current_gpo;
std::vector<GpoEntry> gpo_list;
const std::vector<std::string> lines = base::SplitString(
net_out, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
LOG_IF(INFO, flags.log_gpo()) << kColorGpo << "Parsing GPO list ("
<< lines.size() << " lines)" << kColorReset;
bool found_separator = false;
for (const std::string& line : lines) {
if (line.find(kGpoToken_Separator) == 0) {
// Separator between entries. Process last gpo if any.
PushGpo(current_gpo, scope, &gpo_list, flags);
current_gpo.Clear();
found_separator = true;
continue;
}
// Collect data
const size_t colon_pos = line.find(":");
if (colon_pos == std::string::npos || colon_pos + 1 >= line.size())
continue;
const std::string key = line.substr(0, colon_pos);
std::string value = line.substr(colon_pos + 1);
base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
bool already_set = false;
bool version_error = false;
bool flags_error = false;
if (key == kGpoToken_Name) {
already_set = !current_gpo.name.empty();
current_gpo.name = value;
} else if (key == kGpoToken_Filesyspath) {
already_set = !current_gpo.filesyspath.empty();
current_gpo.filesyspath = value;
} else if (key == kGpoToken_VersionUser) {
already_set = current_gpo.version_user != 0;
version_error = !ParseGpoVersion(value, ¤t_gpo.version_user);
} else if (key == kGpoToken_VersionMachine) {
already_set = current_gpo.version_machine != 0;
version_error = !ParseGpoVersion(value, ¤t_gpo.version_machine);
} else if (key == kGpoToken_Options) {
already_set = current_gpo.gp_flags != kGpFlagInvalid;
flags_error = !ParseGpFlags(value, ¤t_gpo.gp_flags);
}
// Sanity check that we don't miss separators between GPOs.
if (already_set) {
LOG(ERROR) << "Failed to parse GPO data (bad format)";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
if (version_error) {
LOG(ERROR) << "Failed to parse GPO version '" << value << "'";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
if (flags_error) {
LOG(ERROR) << "Failed to parse GP flags '" << value << "'";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
}
// Just in case there's no separator in the end.
PushGpo(current_gpo, scope, &gpo_list, flags);
if (!found_separator) {
// This usually happens when something went wrong, e.g. connection error.
LOG(ERROR) << "Failed to parse GPO data (no separator, did net fail?)";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
if (flags.log_gpo() && LOG_IS_ON(INFO)) {
LOG(INFO) << kColorGpo << "Found " << gpo_list.size() << " GPOs."
<< kColorReset;
for (size_t n = 0; n < gpo_list.size(); ++n) {
LOG(INFO) << kColorGpo << n + 1 << ")" << kColorReset;
gpo_list[n].Log();
}
}
// Convert to proto.
protos::GpoList gpo_list_proto;
for (const GpoEntry& gpo : gpo_list) {
// Split the filesyspath, e.g.
// \\chrome.lan\SysVol\chrome.lan\Policies\{3507856D-...-CF144DC5CC3A}
// into
// - the share (SysVol) and
// - the directory (chrome.lan\Policies\...).
// The first part (chrome.lan) is dropped and replaced by the domain
// controller name when the GPOs are downloaded via smbclient.
const std::vector<std::string> file_parts = base::SplitString(
gpo.filesyspath, "\\/", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (file_parts.size() < 4 || !file_parts[0].empty() ||
!file_parts[1].empty()) {
LOG(ERROR) << "Failed to split filesyspath '" << gpo.filesyspath
<< "' into service and directory parts";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
const std::string& share = file_parts[3];
const std::string directory = base::JoinString(
std::vector<std::string>(file_parts.begin() + 4, file_parts.end()),
"\\");
uint32_t version =
scope == PolicyScope::USER ? gpo.version_user : gpo.version_machine;
protos::GpoEntry* gpo_proto = gpo_list_proto.add_entries();
gpo_proto->set_name(gpo.name);
gpo_proto->set_share(share);
gpo_proto->set_directory(directory);
gpo_proto->set_version(version);
}
// Output data as proto blob.
std::string gpo_list_blob;
if (!gpo_list_proto.SerializeToString(&gpo_list_blob)) {
LOG(ERROR) << "Failed to convert GPO list proto to string";
return EXIT_CODE_WRITE_OUTPUT_FAILED;
}
return OutputForCaller(gpo_list_blob);
}
// Parses a set of GPO files and assembles a user or device policy proto. Writes
// the serialized policy blob to stdout. |gpo_file_paths_blob| is expected to be
// a serialized |protos::FilePathList| proto blob.
int ParsePreg(const std::string& gpo_file_paths_blob,
PolicyScope scope,
const protos::DebugFlags& flags) {
// Parse FilePathList proto blob.
protos::FilePathList gpo_file_paths_proto;
if (!gpo_file_paths_proto.ParseFromString(gpo_file_paths_blob)) {
LOG(ERROR) << "Failed to parse file paths blob";
return EXIT_CODE_READ_INPUT_FAILED;
}
// Convert to list of base::FilePaths.
std::vector<base::FilePath> gpo_file_paths;
for (int n = 0; n < gpo_file_paths_proto.entries_size(); ++n)
gpo_file_paths.push_back(base::FilePath(gpo_file_paths_proto.entries(n)));
protos::GpoPolicyData data;
switch (scope) {
case PolicyScope::USER: {
// Parse files into a user policy proto.
em::CloudPolicySettings policy;
if (!policy::ParsePRegFilesIntoUserPolicy(gpo_file_paths, &policy,
flags.log_policy_values())) {
return EXIT_CODE_PARSE_INPUT_FAILED;
}
// Serialize user policy proto to string.
if (!policy.SerializeToString(data.mutable_user_or_device_policy()))
return EXIT_CODE_WRITE_OUTPUT_FAILED;
break;
}
case PolicyScope::MACHINE: {
// Parse files into a device policy proto.
em::ChromeDeviceSettingsProto policy;
if (!policy::ParsePRegFilesIntoDevicePolicy(gpo_file_paths, &policy,
flags.log_policy_values())) {
return EXIT_CODE_PARSE_INPUT_FAILED;
}
// Serialize policy proto to string.
if (!policy.SerializeToString(data.mutable_user_or_device_policy()))
return EXIT_CODE_WRITE_OUTPUT_FAILED;
break;
}
default: {
LOG(FATAL) << "invalid scope";
}
}
// Parse GPOs again for extension policy. Note that it might be contained in
// both scopes (USER and MACHINE). Note that this is slightly inefficient as
// it loads and parses each GPO file a second time. It would be better if
// preg_parser accepted multiple keys.
policy::ExtensionPolicies extension_policies;
if (!policy::ParsePRegFilesIntoExtensionPolicy(
gpo_file_paths, &extension_policies, flags.log_policy_values())) {
return EXIT_CODE_PARSE_INPUT_FAILED;
}
for (protos::ExtensionPolicy& proto : extension_policies)
*data.add_extension_policies() = std::move(proto);
// Output |data| as serialized string to stdout.
std::string data_blob;
if (!data.SerializeToString(&data_blob))
return EXIT_CODE_WRITE_OUTPUT_FAILED;
return OutputForCaller(data_blob);
}
// Parses the validity and renewal lifetimes of a TGT from the output of klist.
// Writes the serialized lifetime protobuf blob to stdout. For sample klist
// output see stub_klist_main.cc.
int ParseTgtLifetime(const std::string& klist_out) {
std::vector<std::string> lines = base::SplitString(
klist_out, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
time_t valid_from, expires, renew_until = 0;
for (size_t n = 0; n < lines.size(); ++n) {
if (Contains(lines[n], "krbtgt/") &&
ParseTgtDateTime(lines[n], kValidFromOffset, &valid_from) &&
ParseTgtDateTime(lines[n], kExpiresOffset, &expires)) {
if (n + 1 < lines.size() &&
base::StartsWith(lines[n + 1], kRenewUntil,
base::CompareCase::SENSITIVE) &&
ParseTgtDateTime(lines[n + 1], kRenewUntilOffset, &renew_until)) {
++n;
}
// If the caller checked klist -s beforehand, the TGT should be valid and
// these warnings should never be printed.
time_t now = time(NULL);
if (now + kTgtWarningGraceTimeSeconds < valid_from) {
LOG(WARNING) << "TGT not yet valid? (now=" << now
<< ", valid_from=" << valid_from << ")";
}
if (now + kTgtWarningGraceTimeSeconds > expires) {
LOG(WARNING) << "TGT already expired? (now=" << now
<< ", expires=" << expires << ")";
}
// Output lifetime as protobuf blob.
protos::TgtLifetime lifetime;
lifetime.set_validity_seconds(std::max<int64_t>(expires - now, 0));
lifetime.set_renewal_seconds(std::max<int64_t>(renew_until - now, 0));
std::string lifetime_blob;
if (!lifetime.SerializeToString(&lifetime_blob)) {
LOG(ERROR) << "Failed to convert lifetime proto to string";
return EXIT_CODE_WRITE_OUTPUT_FAILED;
}
return OutputForCaller(lifetime_blob);
}
}
LOG(ERROR) << "Failed to find krbtgt in klist output";
return EXIT_CODE_PARSE_INPUT_FAILED;
}
int HandleCommand(const std::string& cmd,
const std::string& arg,
const protos::DebugFlags& flags) {
if (cmd == kCmdParseServerInfo)
return ParseServerInfo(arg);
if (cmd == kCmdParseDcName)
return ParseSingleToken(arg, kToken_DomainController);
if (cmd == kCmdParseWorkgroup)
return ParseSingleToken(arg, kToken_Workgroup);
if (cmd == kCmdParseAccountInfo)
return ParseAccountInfo(arg);
if (cmd == kCmdParseUserGpoList)
return ParseGpoList(arg, PolicyScope::USER, flags);
if (cmd == kCmdParseDeviceGpoList)
return ParseGpoList(arg, PolicyScope::MACHINE, flags);
if (cmd == kCmdParseUserPreg)
return ParsePreg(arg, PolicyScope::USER, flags);
if (cmd == kCmdParseDevicePreg)
return ParsePreg(arg, PolicyScope::MACHINE, flags);
if (cmd == kCmdParseTgtLifetime)
return ParseTgtLifetime(arg);
LOG(ERROR) << "Bad command";
return EXIT_CODE_BAD_COMMAND;
}
} // namespace
} // namespace authpolicy
int main(int argc, char* argv[]) {
brillo::OpenLog("authpolicy_parser", true);
brillo::InitLog(brillo::kLogToSyslog);
// Required for base::SysInfo.
base::AtExitManager at_exit_manager;
// Require one argument, one of the kCmdParse* strings.
if (argc <= 1) {
LOG(ERROR) << "No command";
return authpolicy::EXIT_CODE_BAD_COMMAND;
}
const char* cmd = argv[1];
// Load debug flags from argv[2] if present.
authpolicy::protos::DebugFlags flags;
if (argc > 2 && !authpolicy::DeserializeFlags(argv[2], &flags)) {
LOG(ERROR) << "Failed to deserialize flags";
return authpolicy::EXIT_CODE_BAD_COMMAND;
}
// All commands take additional arguments via stdin.
std::string stdin;
if (!authpolicy::ReadPipeToString(STDIN_FILENO, &stdin)) {
LOG(ERROR) << "Failed to read stdin";
return authpolicy::EXIT_CODE_READ_INPUT_FAILED;
}
return authpolicy::HandleCommand(cmd, stdin, flags);
}