1 /*
2 Copyright 2020 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <server_transform.h>
26
27 #include <server.h>
28
29 #include <misc_lib.h>
30 #include <eval_context.h>
31 #include <files_names.h>
32 #include <mod_common.h>
33 #include <mod_access.h>
34 #include <item_lib.h>
35 #include <conversion.h>
36 #include <ornaments.h>
37 #include <expand.h>
38 #include <scope.h>
39 #include <vars.h>
40 #include <attributes.h>
41 #include <communication.h>
42 #include <string_lib.h>
43 #include <rlist.h>
44 #include <cf-serverd-enterprise-stubs.h>
45 #include <syslog_client.h>
46 #include <verify_classes.h>
47 #include <verify_vars.h>
48 #include <generic_agent.h> /* HashControls */
49 #include <file_lib.h> /* IsDirReal */
50 #include <matching.h> /* IsRegex */
51 #include <net.h>
52 #include <client_code.h>
53 #include <cfnet.h>
54 #include <known_dirs.h> // GetWorkDir()
55
56 #include "server_common.h" /* PreprocessRequestPath */
57 #include "server_access.h"
58 #include "strlist.h"
59 #include <cleanup.h>
60
61
62 static PromiseResult KeepServerPromise(EvalContext *ctx, const Promise *pp, void *param);
63 static void InstallServerAuthPath(const char *path, Auth **list, Auth **listtail);
64 static void KeepServerRolePromise(EvalContext *ctx, const Promise *pp);
65 static void KeepPromiseBundles(EvalContext *ctx, const Policy *policy);
66 static void KeepControlPromises(EvalContext *ctx, const Policy *policy, GenericAgentConfig *config);
67 static void KeepBundlesAccessPromise(EvalContext *ctx, const Promise *pp);
68 static Auth *GetAuthPath(const char *path, Auth *list);
69
70
71 extern int COLLECT_INTERVAL;
72 extern int COLLECT_WINDOW;
73 extern bool SERVER_LISTEN;
74
75
76 /*******************************************************************/
77 /* GLOBAL VARIABLES */
78 /*******************************************************************/
79
80 extern int CFD_MAXPROCESSES;
81 extern int NO_FORK;
82 extern bool DENYBADCLOCKS;
83 extern int MAXTRIES;
84 extern bool LOGENCRYPT;
85
86 /*******************************************************************/
87
88 static void KeepFileAccessPromise(const EvalContext *ctx, const Promise *pp);
89 static void KeepLiteralAccessPromise(EvalContext *ctx, const Promise *pp, const char *type);
90 static void KeepQueryAccessPromise(EvalContext *ctx, const Promise *pp);
91
92 /*******************************************************************/
93 /* Level */
94 /*******************************************************************/
95
96
Summarize()97 void Summarize()
98 {
99 Auth *ptr;
100 Item *ip, *ipr;
101
102 Log(LOG_LEVEL_VERBOSE, " === BEGIN summary of access promises === ");
103
104 Log(LOG_LEVEL_VERBOSE,
105 "Host IPs allowed connection access (allowconnects):");
106 for (ip = SERVER_ACCESS.nonattackerlist; ip != NULL; ip = ip->next)
107 {
108 Log(LOG_LEVEL_VERBOSE, "\tIP: %s", ip->name);
109 }
110
111 Log(LOG_LEVEL_VERBOSE,
112 "Host IPs denied connection access (denyconnects):");
113 for (ip = SERVER_ACCESS.attackerlist; ip != NULL; ip = ip->next)
114 {
115 Log(LOG_LEVEL_VERBOSE, "\tIP: %s", ip->name);
116 }
117
118 Log(LOG_LEVEL_VERBOSE,
119 "Host IPs allowed multiple connection access (allowallconnects):");
120 for (ip = SERVER_ACCESS.multiconnlist; ip != NULL; ip = ip->next)
121 {
122 Log(LOG_LEVEL_VERBOSE, "\tIP: %s", ip->name);
123 }
124
125 Log(LOG_LEVEL_VERBOSE,
126 "Host IPs whose keys we shall establish trust to (trustkeysfrom):");
127 for (ip = SERVER_ACCESS.trustkeylist; ip != NULL; ip = ip->next)
128 {
129 Log(LOG_LEVEL_VERBOSE, "\tIP: %s", ip->name);
130 }
131
132 Log(LOG_LEVEL_VERBOSE,
133 "Host IPs allowed legacy connections (allowlegacyconnects):");
134 for (ip = SERVER_ACCESS.allowlegacyconnects; ip != NULL; ip = ip->next)
135 {
136 Log(LOG_LEVEL_VERBOSE, "\tIP: %s", ip->name);
137 }
138
139 Log(LOG_LEVEL_VERBOSE,
140 "Users from whom we accept cf-runagent connections (allowusers):");
141 for (ip = SERVER_ACCESS.allowuserlist; ip != NULL; ip = ip->next)
142 {
143 Log(LOG_LEVEL_VERBOSE, "\tUSER: %s", ip->name);
144 }
145
146 Log(LOG_LEVEL_VERBOSE, "Access control lists:");
147 acl_Summarise(paths_acl, "Path");
148 acl_Summarise(classes_acl, "Class");
149 acl_Summarise(vars_acl, "Variable");
150 acl_Summarise(literals_acl, "Literal");
151 acl_Summarise(query_acl, "Query");
152 acl_Summarise(roles_acl, "Role");
153 acl_Summarise(bundles_acl, "Bundle");
154
155 Log(LOG_LEVEL_VERBOSE,
156 "Access control lists for the classic network protocol:");
157
158 for (ptr = SERVER_ACCESS.admit; ptr != NULL; ptr = ptr->next)
159 {
160 /* Don't report empty entries. */
161 if (ptr->maproot != NULL || ptr->accesslist != NULL)
162 {
163 Log(LOG_LEVEL_VERBOSE, "\tPath: %s", ptr->path);
164 }
165
166 for (ipr = ptr->maproot; ipr != NULL; ipr = ipr->next)
167 {
168 Log(LOG_LEVEL_VERBOSE, "\t\tmaproot user: %s,", ipr->name);
169 }
170
171 for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
172 {
173 Log(LOG_LEVEL_VERBOSE, "\t\tadmit: %s", ip->name);
174 }
175 }
176
177 for (ptr = SERVER_ACCESS.deny; ptr != NULL; ptr = ptr->next)
178 {
179 /* Don't report empty entries. */
180 if (ptr->accesslist != NULL)
181 {
182 Log(LOG_LEVEL_VERBOSE, "\tPath: %s", ptr->path);
183 }
184
185 for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
186 {
187 Log(LOG_LEVEL_VERBOSE, "\t\tdeny: %s", ip->name);
188 }
189 }
190
191 for (ptr = SERVER_ACCESS.varadmit; ptr != NULL; ptr = ptr->next)
192 {
193 Log(LOG_LEVEL_VERBOSE, "Object: %s", ptr->path);
194
195 for (ipr = ptr->maproot; ipr != NULL; ipr = ipr->next)
196 {
197 Log(LOG_LEVEL_VERBOSE, "%s,", ipr->name);
198 }
199 for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
200 {
201 Log(LOG_LEVEL_VERBOSE, "Admit '%s' root=", ip->name);
202 }
203 }
204
205 for (ptr = SERVER_ACCESS.vardeny; ptr != NULL; ptr = ptr->next)
206 {
207 Log(LOG_LEVEL_VERBOSE, "Object %s", ptr->path);
208
209 for (ip = ptr->accesslist; ip != NULL; ip = ip->next)
210 {
211 Log(LOG_LEVEL_VERBOSE, "Deny '%s'", ip->name);
212 }
213 }
214
215 Log(LOG_LEVEL_VERBOSE, " === END summary of access promises === ");
216 }
217
KeepPromises(EvalContext * ctx,const Policy * policy,GenericAgentConfig * config)218 void KeepPromises(EvalContext *ctx, const Policy *policy, GenericAgentConfig *config)
219 {
220 if (paths_acl != NULL || classes_acl != NULL || vars_acl != NULL ||
221 literals_acl != NULL || query_acl != NULL || bundles_acl != NULL ||
222 roles_acl != NULL || SERVER_ACCESS.path_shortcuts != NULL)
223 {
224 UnexpectedError("ACLs are not NULL - we are probably leaking memory!");
225 }
226
227 paths_acl = calloc(1, sizeof(*paths_acl));
228 classes_acl = calloc(1, sizeof(*classes_acl));
229 vars_acl = calloc(1, sizeof(*vars_acl));
230 literals_acl = calloc(1, sizeof(*literals_acl));
231 query_acl = calloc(1, sizeof(*query_acl));
232 bundles_acl = calloc(1, sizeof(*bundles_acl));
233 roles_acl = calloc(1, sizeof(*roles_acl));
234 SERVER_ACCESS.path_shortcuts = StringMapNew();
235
236 if (paths_acl == NULL || classes_acl == NULL || vars_acl == NULL ||
237 literals_acl == NULL || query_acl == NULL || bundles_acl == NULL ||
238 roles_acl == NULL || SERVER_ACCESS.path_shortcuts == NULL)
239 {
240 Log(LOG_LEVEL_CRIT, "calloc: %s", GetErrorStr());
241 DoCleanupAndExit(255);
242 }
243
244 KeepControlPromises(ctx, policy, config);
245 KeepPromiseBundles(ctx, policy);
246 }
247
248 /*******************************************************************/
249
SetMaxOpenFiles(int n)250 static bool SetMaxOpenFiles(int n)
251 #ifdef HAVE_SYS_RESOURCE_H
252 {
253 const struct rlimit lim = {
254 .rlim_cur = n,
255 .rlim_max = n,
256 };
257 int ret = setrlimit(RLIMIT_NOFILE, &lim);
258 if (ret == -1)
259 {
260 Log(LOG_LEVEL_INFO, "Failed setting max open files limit"
261 " (setrlimit(NOFILE, %d): %s)", n, GetErrorStr());
262 Log(LOG_LEVEL_INFO,
263 "Please ensure that 'nofile' ulimit is at least 5x maxconnections");
264 return false;
265 }
266 else
267 {
268 Log(LOG_LEVEL_VERBOSE, "Setting max open files rlimit to %d", n);
269 return true;
270 }
271 }
272 #else /* MinGW */
273 {
274 Log(LOG_LEVEL_VERBOSE,
275 "Platform does not support setrlimit(NOFILE), max open files not set");
276 return false;
277 }
278 #endif /* HAVE_SYS_RESOURCE_H */
279
KeepControlPromises(EvalContext * ctx,const Policy * policy,GenericAgentConfig * config)280 static void KeepControlPromises(EvalContext *ctx, const Policy *policy, GenericAgentConfig *config)
281 {
282 CFD_MAXPROCESSES = 30;
283 MAXTRIES = 5;
284 DENYBADCLOCKS = true;
285 CFRUNCOMMAND[0] = '\0';
286 SetChecksumUpdatesDefault(ctx, true);
287
288 /* Keep promised agent behaviour - control bodies */
289
290 Banner("Server control promises..");
291
292 PolicyResolve(ctx, policy, config);
293
294 /* Now expand */
295
296 Seq *constraints = ControlBodyConstraints(policy, AGENT_TYPE_SERVER);
297
298 #define IsControlBody(e) (strcmp(cp->lval, CFS_CONTROLBODY[e].lval) == 0)
299
300 if (constraints)
301 {
302 for (size_t i = 0; i < SeqLength(constraints); i++)
303 {
304 Constraint *cp = SeqAt(constraints, i);
305
306 if (!IsDefinedClass(ctx, cp->classes))
307 {
308 continue;
309 }
310
311 VarRef *ref = VarRefParseFromScope(cp->lval, "control_server");
312 const void *value = EvalContextVariableGet(ctx, ref, NULL);
313 VarRefDestroy(ref);
314
315 if (IsControlBody(SERVER_CONTROL_SERVER_FACILITY))
316 {
317 SetFacility(value);
318 }
319 else if (IsControlBody(SERVER_CONTROL_DENY_BAD_CLOCKS))
320 {
321 DENYBADCLOCKS = BooleanFromString(value);
322 Log(LOG_LEVEL_VERBOSE,
323 "Setting denybadclocks to '%s'",
324 DENYBADCLOCKS ? "true" : "false");
325 }
326 else if (IsControlBody(SERVER_CONTROL_LOG_ENCRYPTED_TRANSFERS))
327 {
328 LOGENCRYPT = BooleanFromString(value);
329 Log(LOG_LEVEL_VERBOSE,
330 "Setting logencrypt to '%s'",
331 LOGENCRYPT ? "true" : "false");
332 }
333 else if (IsControlBody(SERVER_CONTROL_LOG_ALL_CONNECTIONS))
334 {
335 SERVER_ACCESS.logconns = BooleanFromString(value);
336 Log(LOG_LEVEL_VERBOSE, "Setting logconns to %d", SERVER_ACCESS.logconns);
337 }
338 else if (IsControlBody(SERVER_CONTROL_MAX_CONNECTIONS))
339 {
340 CFD_MAXPROCESSES = (int) IntFromString(value);
341
342 /* Ease apoptosis limits. */
343 MAXTRIES = CFD_MAXPROCESSES / 3;
344
345 /* The handling of max_readers in LMDB is not ideal, but
346 * here is how it is right now: We know that both cf-serverd and
347 * cf-hub will access the lastseen database. Worst case every
348 * single thread and process will do it at the same time, and
349 * this has in fact been observed. So we add the maximum of
350 * those two values together to provide a safe ceiling. In
351 * addition, cf-agent can access the database occasionally as
352 * well, so add a few extra for that too. */
353 Log(LOG_LEVEL_VERBOSE,
354 "Setting maxconnections to %d", CFD_MAXPROCESSES);
355 DBSetMaximumConcurrentTransactions(CFD_MAXPROCESSES
356 + EnterpriseGetMaxCfHubProcesses() + 10);
357
358 /* Set RLIMIT_NOFILE to be enough for all threads. */
359 SetMaxOpenFiles(CFD_MAXPROCESSES * 5 + 10);
360 }
361 else if (IsControlBody(SERVER_CONTROL_CALL_COLLECT_INTERVAL))
362 {
363 COLLECT_INTERVAL = (int) 60 * IntFromString(value);
364 Log(LOG_LEVEL_VERBOSE,
365 "Setting call_collect_interval to %d (seconds)",
366 COLLECT_INTERVAL);
367 }
368 else if (IsControlBody(SERVER_CONTROL_LISTEN))
369 {
370 SERVER_LISTEN = BooleanFromString(value);
371 Log(LOG_LEVEL_VERBOSE,
372 "Setting server listen to '%s' ",
373 SERVER_LISTEN ? "true" : "false");
374 }
375 else if (IsControlBody(SERVER_CONTROL_CALL_COLLECT_WINDOW))
376 {
377 COLLECT_WINDOW = (int) IntFromString(value);
378 Log(LOG_LEVEL_VERBOSE,
379 "Setting collect_window to %d (seconds)",
380 COLLECT_WINDOW);
381 }
382 else if (IsControlBody(SERVER_CONTROL_CFRUNCOMMAND))
383 {
384 if (strlen(value) >= sizeof(CFRUNCOMMAND))
385 {
386 Log(LOG_LEVEL_ERR,
387 "cfruncommand too long (>%zu), leaving empty",
388 sizeof(CFRUNCOMMAND));
389 }
390 else
391 {
392 memcpy(CFRUNCOMMAND, value, strlen(value) + 1);
393 Log(LOG_LEVEL_VERBOSE, "Setting cfruncommand to: %s",
394 CFRUNCOMMAND);
395 }
396 }
397 else if (IsControlBody(SERVER_CONTROL_ALLOW_CONNECTS))
398 {
399 Log(LOG_LEVEL_VERBOSE, "Setting allowing connections from ...");
400
401 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
402 {
403 if (!IsItemIn(SERVER_ACCESS.nonattackerlist, RlistScalarValue(rp)))
404 {
405 PrependItem(&SERVER_ACCESS.nonattackerlist, RlistScalarValue(rp), cp->classes);
406 }
407 }
408 }
409 else if (IsControlBody(SERVER_CONTROL_DENY_CONNECTS))
410 {
411 Log(LOG_LEVEL_VERBOSE, "Setting denying connections from ...");
412
413 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
414 {
415 if (!IsItemIn(SERVER_ACCESS.attackerlist, RlistScalarValue(rp)))
416 {
417 PrependItem(&SERVER_ACCESS.attackerlist, RlistScalarValue(rp), cp->classes);
418 }
419 }
420 }
421 else if (IsControlBody(SERVER_CONTROL_SKIP_VERIFY))
422 {
423 /* Skip. */
424 }
425 else if (IsControlBody(SERVER_CONTROL_ALLOW_ALL_CONNECTS))
426 {
427 Log(LOG_LEVEL_VERBOSE, "Setting allowing multiple connections from ...");
428
429 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
430 {
431 if (!IsItemIn(SERVER_ACCESS.multiconnlist, RlistScalarValue(rp)))
432 {
433 PrependItem(&SERVER_ACCESS.multiconnlist, RlistScalarValue(rp), cp->classes);
434 }
435 }
436 }
437 else if (IsControlBody(SERVER_CONTROL_ALLOW_USERS))
438 {
439 Log(LOG_LEVEL_VERBOSE, "SET Allowing users ...");
440
441 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
442 {
443 if (!IsItemIn(SERVER_ACCESS.allowuserlist, RlistScalarValue(rp)))
444 {
445 PrependItem(&SERVER_ACCESS.allowuserlist, RlistScalarValue(rp), cp->classes);
446 }
447 }
448 }
449 else if (IsControlBody(SERVER_CONTROL_TRUST_KEYS_FROM))
450 {
451 Log(LOG_LEVEL_VERBOSE, "Setting 'trustkeysfrom' ...");
452
453 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
454 {
455 if (!IsItemIn(SERVER_ACCESS.trustkeylist, RlistScalarValue(rp)))
456 {
457 PrependItem(&SERVER_ACCESS.trustkeylist, RlistScalarValue(rp), cp->classes);
458 }
459 }
460 }
461 else if (IsControlBody(SERVER_CONTROL_ALLOWLEGACYCONNECTS))
462 {
463 Log(LOG_LEVEL_VERBOSE, "Setting 'allowlegacyconnects' ...");
464
465 for (const Rlist *rp = value; rp != NULL; rp = rp->next)
466 {
467 if (!IsItemIn(SERVER_ACCESS.allowlegacyconnects, RlistScalarValue(rp)))
468 {
469 PrependItem(&SERVER_ACCESS.allowlegacyconnects, RlistScalarValue(rp), cp->classes);
470 }
471 }
472 }
473 else if (IsControlBody(SERVER_CONTROL_PORT_NUMBER))
474 {
475 bool ret = SetCfenginePort(value);
476 assert(ret);
477 if (ret == false)
478 {
479 Log(LOG_LEVEL_VERBOSE, "Could not set port number, continuing (See errors above)");
480 }
481 }
482 else if (IsControlBody(SERVER_CONTROL_BIND_TO_INTERFACE))
483 {
484 SetBindInterface(value);
485 }
486 else if (IsControlBody(SERVER_CONTROL_ALLOWCIPHERS))
487 {
488 assert(SERVER_ACCESS.allowciphers == NULL); /* no leak */
489 SERVER_ACCESS.allowciphers = xstrdup(value);
490 Log(LOG_LEVEL_VERBOSE, "Setting allowciphers to: %s",
491 SERVER_ACCESS.allowciphers);
492 }
493 else if (IsControlBody(SERVER_CONTROL_ALLOWTLSVERSION))
494 {
495 assert(SERVER_ACCESS.allowtlsversion == NULL); /* no leak */
496 SERVER_ACCESS.allowtlsversion = xstrdup(value);
497 Log(LOG_LEVEL_VERBOSE, "Setting allowtlsversion to: %s",
498 SERVER_ACCESS.allowtlsversion);
499 }
500 }
501
502 #undef IsControlBody
503
504 }
505
506 const void *value = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_SYSLOG_HOST);
507 if (value)
508 {
509 /* Don't resolve syslog_host now, better do it per log request. */
510 if (!SetSyslogHost(value))
511 {
512 Log(LOG_LEVEL_ERR, "Failed to set syslog_host, '%s' too long", (const char *)value);
513 }
514 else
515 {
516 Log(LOG_LEVEL_VERBOSE, "Setting syslog_host to '%s'", (const char *)value);
517 }
518 }
519
520 value = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_SYSLOG_PORT);
521 if (value)
522 {
523 SetSyslogPort(IntFromString(value));
524 }
525
526 value = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_FIPS_MODE);
527 if (value)
528 {
529 FIPS_MODE = BooleanFromString(value);
530 Log(LOG_LEVEL_VERBOSE, "Setting FIPS mode to to '%s'", FIPS_MODE ? "true" : "false");
531 }
532
533 value = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_LASTSEEN_EXPIRE_AFTER);
534 if (value)
535 {
536 LASTSEENEXPIREAFTER = IntFromString(value) * 60;
537 }
538
539 value = EvalContextVariableControlCommonGet(ctx, COMMON_CONTROL_BWLIMIT);
540 if (value)
541 {
542 double bval;
543 if (DoubleFromString(value, &bval))
544 {
545 bwlimit_kbytes = (uint32_t) ( bval / 1000.0);
546 Log(LOG_LEVEL_VERBOSE, "Setting rate limit to %d kBytes/sec", bwlimit_kbytes);
547 }
548 }
549
550 if (config->agent_type == AGENT_TYPE_SERVER)
551 {
552 EvalContextUpdateDumpReports(ctx);
553 }
554 }
555
556 /*********************************************************************/
557
558 /* Sequence in which server promise types should be evaluated */
559 static const char *const SERVER_TYPESEQUENCE[] =
560 {
561 "meta",
562 "vars",
563 "classes",
564 "roles",
565 "access",
566 NULL
567 };
568
569 static const char *const COMMON_TYPESEQUENCE[] =
570 {
571 "meta",
572 "vars",
573 "classes",
574 "reports",
575 NULL
576 };
577
578 /* Check if promise is NOT belonging to default server types
579 * (see SERVER_TYPESEQUENCE)*/
IsPromiseTypeNotInTypeSequence(const char * promise_type,const char * const * seq)580 static bool IsPromiseTypeNotInTypeSequence(const char *promise_type,
581 const char * const *seq)
582 {
583 for (int type = 0; seq[type] != NULL; type++)
584 {
585 if (strcmp(promise_type, seq[type]) == 0)
586 {
587 return false;
588 }
589 }
590 return true;
591 }
592
EvaluateBundle(EvalContext * ctx,const Bundle * bp,const char * const * seq)593 static void EvaluateBundle(EvalContext *ctx, const Bundle *bp, const char * const *seq)
594 {
595 EvalContextStackPushBundleFrame(ctx, bp, NULL, false);
596
597 for (int type = 0; seq[type] != NULL; type++)
598 {
599 const BundleSection *sp = BundleGetSection((Bundle *)bp, seq[type]);
600
601 /* Some promise types might not be there. */
602 if (!sp || SeqLength(sp->promises) == 0)
603 {
604 Log(LOG_LEVEL_DEBUG, "No promise type %s in bundle %s",
605 seq[type], bp->name);
606 continue;
607 }
608
609 EvalContextStackPushBundleSectionFrame(ctx, sp);
610 for (size_t ppi = 0; ppi < SeqLength(sp->promises); ppi++)
611 {
612 Promise *pp = SeqAt(sp->promises, ppi);
613 ExpandPromise(ctx, pp, KeepServerPromise, NULL);
614 }
615 EvalContextStackPopFrame(ctx);
616 }
617
618 /* Check if we are having some other promise types which we
619 * should evaluate. THIS IS ONLY FOR BACKWARD COMPATIBILITY! */
620 for (size_t j = 0; j < SeqLength(bp->sections); j++)
621 {
622 BundleSection *sp = SeqAt(bp->sections, j);
623
624 /* Skipping evaluation of promise as this was evaluated in
625 * loop above. */
626 if (!IsPromiseTypeNotInTypeSequence(sp->promise_type, seq))
627 {
628 Log(LOG_LEVEL_DEBUG, "Skipping subsequent evaluation of "
629 "promise type %s in bundle %s", sp->promise_type, bp->name);
630 continue;
631 }
632
633 Log(LOG_LEVEL_WARNING, "Trying to evaluate unsupported/obsolete "
634 "promise type %s in %s bundle %s", sp->promise_type, bp->type, bp->name);
635
636 EvalContextStackPushBundleSectionFrame(ctx, sp);
637 for (size_t ppi = 0; ppi < SeqLength(sp->promises); ppi++)
638 {
639 Promise *pp = SeqAt(sp->promises, ppi);
640 ExpandPromise(ctx, pp, KeepServerPromise, NULL);
641 }
642 EvalContextStackPopFrame(ctx);
643
644 }
645
646 EvalContextStackPopFrame(ctx);
647 }
648
KeepPromiseBundles(EvalContext * ctx,const Policy * policy)649 static void KeepPromiseBundles(EvalContext *ctx, const Policy *policy)
650 {
651 /* Dial up the generic promise expansion with a callback */
652
653 CleanReportBookFilterSet();
654
655 for (size_t i = 0; i < SeqLength(policy->bundles); i++)
656 {
657 Bundle *bp = SeqAt(policy->bundles, i);
658 bool server_bundle = strcmp(bp->type, CF_AGENTTYPES[AGENT_TYPE_SERVER]) == 0;
659 bool common_bundle = strcmp(bp->type, CF_AGENTTYPES[AGENT_TYPE_COMMON]) == 0;
660
661 if (server_bundle || common_bundle)
662 {
663 if (RlistLen(bp->args) > 0)
664 {
665 Log(LOG_LEVEL_WARNING,
666 "Cannot implicitly evaluate bundle '%s %s', as this bundle takes arguments.",
667 bp->type, bp->name);
668 continue;
669 }
670 }
671
672 if (server_bundle)
673 {
674 EvaluateBundle(ctx, bp, SERVER_TYPESEQUENCE);
675 }
676
677 else if (common_bundle)
678 {
679 EvaluateBundle(ctx, bp, COMMON_TYPESEQUENCE);
680 }
681 }
682 }
683
KeepServerPromise(EvalContext * ctx,const Promise * pp,ARG_UNUSED void * param)684 static PromiseResult KeepServerPromise(EvalContext *ctx, const Promise *pp, ARG_UNUSED void *param)
685 {
686 assert(!param);
687 PromiseBanner(ctx, pp);
688
689 if (strcmp(pp->parent_section->promise_type, "vars") == 0)
690 {
691 return VerifyVarPromise(ctx, pp, NULL);
692 }
693
694 if (strcmp(pp->parent_section->promise_type, "classes") == 0)
695 {
696 return VerifyClassPromise(ctx, pp, NULL);
697 }
698
699 /* Warn if promise locking was used with a promise that doesn't support it.
700 * That applies to both of the below promise types while 'vars' and
701 * 'classes' handle this on their own. */
702 int ifelapsed = PromiseGetConstraintAsInt(ctx, "ifelapsed", pp);
703 if (ifelapsed != CF_NOINT)
704 {
705 Log(LOG_LEVEL_WARNING,
706 "ifelapsed attribute specified in action body for %s promise '%s',"
707 " but %s promises do not support promise locking",
708 pp->parent_section->promise_type, pp->promiser,
709 pp->parent_section->promise_type);
710 }
711 int expireafter = PromiseGetConstraintAsInt(ctx, "expireafter", pp);
712 if (expireafter != CF_NOINT)
713 {
714 Log(LOG_LEVEL_WARNING,
715 "expireafter attribute specified in action body for %s promise '%s',"
716 " but %s promises do not support promise locking",
717 pp->parent_section->promise_type, pp->promiser,
718 pp->parent_section->promise_type);
719 }
720
721 if (strcmp(pp->parent_section->promise_type, "access") == 0)
722 {
723 const char *resource_type =
724 PromiseGetConstraintAsRval(pp, "resource_type", RVAL_TYPE_SCALAR);
725
726 /* Default resource_type in access_rules is "path" */
727 if (resource_type == NULL ||
728 strcmp(resource_type, "path") == 0)
729 {
730 KeepFileAccessPromise(ctx, pp);
731 return PROMISE_RESULT_NOOP;
732 }
733 else if (strcmp(resource_type, "literal") == 0)
734 {
735 KeepLiteralAccessPromise(ctx, pp, "literal");
736 return PROMISE_RESULT_NOOP;
737 }
738 else if (strcmp(resource_type, "variable") == 0)
739 {
740 KeepLiteralAccessPromise(ctx, pp, "variable");
741 return PROMISE_RESULT_NOOP;
742 }
743 else if (strcmp(resource_type, "query") == 0)
744 {
745 KeepQueryAccessPromise(ctx, pp);
746 KeepReportDataSelectAccessPromise(pp);
747 return PROMISE_RESULT_NOOP;
748 }
749 else if (strcmp(resource_type, "context") == 0)
750 {
751 KeepLiteralAccessPromise(ctx, pp, "context");
752 return PROMISE_RESULT_NOOP;
753 }
754 else if (strcmp(resource_type, "bundle") == 0)
755 {
756 KeepBundlesAccessPromise(ctx, pp);
757 return PROMISE_RESULT_NOOP;
758 }
759 }
760 else if (strcmp(pp->parent_section->promise_type, "roles") == 0)
761 {
762 KeepServerRolePromise(ctx, pp);
763 return PROMISE_RESULT_NOOP;
764 }
765
766 return PROMISE_RESULT_NOOP;
767 }
768
769 /*********************************************************************/
770
771 enum admit_type
772 {
773 ADMIT_TYPE_IP,
774 ADMIT_TYPE_HOSTNAME,
775 ADMIT_TYPE_KEY,
776 ADMIT_TYPE_OTHER
777 };
778
779 /* Check if the given string is an IP subnet, a hostname, a key, or none of
780 * the above. */
AdmitType(const char * s)781 static enum admit_type AdmitType(const char *s)
782 {
783 if (strncmp(s, "SHA=", strlen("SHA=")) == 0 ||
784 strncmp(s, "MD5=", strlen("MD5=")) == 0)
785 {
786 return ADMIT_TYPE_KEY;
787 }
788 /* IPv4 or IPv6 subnet mask or regex. */
789 /* TODO change this to "0123456789abcdef.:/", no regex allowed. */
790 else if (s[strspn(s, "0123456789abcdef.:/[-]*()\\")] == '\0')
791 {
792 return ADMIT_TYPE_IP;
793 }
794 else
795 {
796 return ADMIT_TYPE_HOSTNAME;
797 }
798 }
799
800 /**
801 * Map old-style regex-or-hostname #host to new-style host-or-domain
802 * and append it to #sl.
803 *
804 * Old-style ACLs could include regexes to be matched against host
805 * names; but new-style ones only support sub-domain matching. If the
806 * old-style host regex looks like ".*\.sub\.domain\.tld" we can take
807 * it in as ".sub.domain.tld"; otherwise, we can only really map exact
808 * match hostnames. However, we know some old policy (including our
809 * own masterfiles) had cases of .*sub.domain.tld and it's possible
810 * that someone might include a new-style .sub.domain.tld by mistake
811 * in an (old-style) accept list; so cope with these cases, too.
812 *
813 * @param sl The string-list to which to add entries.
814 * @param host The name-or-regex to add to the ACL.
815 * @return An index at which an entry was added to the list (there may
816 * be another), or -1 if nothing added.
817 */
StrList_AppendRegexHostname(StrList ** sl,const char * host)818 static size_t StrList_AppendRegexHostname(StrList **sl, const char *host)
819 {
820 if (IsRegex(host))
821 {
822 if (host[strcspn(host, "({[|+?]})")] != '\0')
823 {
824 return -1; /* Not a regex we can sensibly massage; discard. */
825 }
826 bool skip[2] = { false, false }; /* { domain, host } passes below */
827 const char *name = host;
828 if (name[0] == '^') /* Was always implicit; but read as hint to intent. */
829 {
830 /* Default to skipping domain-form if anchored explicitly: */
831 skip[0] = true; /* Over-ridden below if followed by .* of course. */
832 name++;
833 }
834 if (StringStartsWith(name, ".*"))
835 {
836 skip[0] = false; /* Domain-form should match */
837 name += 2;
838 }
839 if (StringStartsWith(name, "\\."))
840 {
841 /* Skip host-form, as the regex definitely wants something
842 * before the given name. */
843 skip[1] = true;
844 name += 2;
845 }
846 if (strchr(name, '*') != NULL)
847 {
848 /* Can't handle a * later than the preamble. */
849 return (size_t) -1;
850 }
851
852 if (name > host ||
853 strchr(host, '\\') != NULL)
854 {
855 /* 2: leading '.' and final '\0' */
856 char copy[2 + strlen(name)], *c = copy;
857 c++[0] = '.'; /* For domain-form; and copy+1 gives host-form. */
858 /* Now copy the rest of the name, de-regex-ifying as we go: */
859 for (const char *p = name; p[0] != '\0'; p++)
860 {
861 if (p[0] == '\\')
862 {
863 p++;
864 if (p[0] != '.')
865 {
866 /* Regex includes a non-dot escape */
867 return (size_t) -1;
868 }
869 }
870 #if 0
871 else if (p[0] == '.')
872 {
873 /* In principle, this is a special character; but
874 * it may just be an unescaped dot, so let it be. */
875 }
876 #endif
877 c++[0] = p[0];
878 }
879 assert(c < copy + sizeof(copy));
880 c[0] = '\0';
881
882 /* Now, for host then domain, add entry if suitable */
883 int pass = 2;
884 size_t ret = -1;
885 while (pass > 0)
886 {
887 pass--;
888 if (!skip[pass]) /* pass 0 is domain, pass 1 is host */
889 {
890 ret = StrList_Append(sl, copy + pass);
891 }
892 }
893 return ret;
894 }
895
896 /* IsRegex() is true but we treat it just as a name! */
897 }
898 /* Just a simple host name. */
899
900 return StrList_Append(sl, host);
901 }
902
903 bool NEED_REVERSE_LOOKUP = false;
904
TurnOnReverseLookups()905 static void TurnOnReverseLookups()
906 {
907 if (!NEED_REVERSE_LOOKUP)
908 {
909 Log(LOG_LEVEL_INFO,
910 "Found hostname admit/deny in access_rules, "
911 "turning on reverse DNS lookups for every connection");
912 NEED_REVERSE_LOOKUP = true;
913 }
914
915 }
916
racl_SmartAppend(struct admitdeny_acl * ad,const char * entry)917 static size_t racl_SmartAppend(struct admitdeny_acl *ad, const char *entry)
918 {
919 size_t ret;
920
921 switch (AdmitType(entry))
922 {
923
924 case ADMIT_TYPE_IP:
925 /* TODO convert IP string to binary representation. */
926 ret = StrList_Append(&ad->ips, entry);
927 break;
928
929 case ADMIT_TYPE_KEY:
930 ret = StrList_Append(&ad->keys, entry);
931 break;
932
933 case ADMIT_TYPE_HOSTNAME:
934 ret = StrList_AppendRegexHostname(&ad->hostnames, entry);
935
936 /* If any hostname rule got added,
937 * turn on reverse DNS lookup in the new protocol. */
938 if (ret != (size_t) -1)
939 {
940 TurnOnReverseLookups();
941 }
942
943 break;
944
945 default:
946 Log(LOG_LEVEL_WARNING,
947 "Access rule 'admit: %s' is not IP, hostname or key, ignoring",
948 entry);
949 ret = (size_t) -1;
950 }
951
952 return ret;
953 }
954
955 /* Package hostname as regex, if needed.
956 *
957 * @param old The old Auth structure to which to add.
958 * @param host The new acl_hostnames entry to add to it.
959 */
NewHostToOldACL(Auth * old,const char * host)960 static void NewHostToOldACL(Auth *old, const char *host)
961 {
962 if (host[0] == '.') /* Domain - transform to regex: */
963 {
964 int extra = 2; /* For leading ".*" */
965 const char *dot = host;
966
967 do
968 {
969 do
970 {
971 dot++; /* Step over prior dot. */
972 } while (dot[0] == '.'); /* Treat many dots as one. */
973 extra++; /* For a backslash before the dot */
974 dot = strchr(dot, '.');
975 } while (dot);
976
977 char regex[strlen(host) + extra], *dst = regex;
978 dst++[0] = '.';
979 dst++[0] = '*';
980
981 dot = host;
982 do
983 {
984 /* Insert literal dot. */
985 assert(dot[0] == '.');
986 dst++[0] = '\\';
987 dst++[0] = '.';
988
989 do /* Step over prior dot(s), as before. */
990 {
991 dot++;
992 } while (dot[0] == '.');
993
994 /* Identify next fragment: */
995 const char *d = strchr(dot, '.');
996 size_t len = d ? d - dot : strlen(dot);
997
998 /* Copy fragment: */
999 memcpy(dst, dot, len);
1000 dst += len;
1001
1002 /* Advance: */
1003 dot = d;
1004 } while (dot);
1005
1006 /* Terminate: */
1007 assert(dst < regex + sizeof(regex));
1008 dst[0] = '\0';
1009
1010 /* Add to list: */
1011 PrependItem(&(old->accesslist), regex, NULL);
1012 }
1013 else
1014 {
1015 /* Simple host-name; just add it: */
1016 PrependItem(&(old->accesslist), host, NULL);
1017 }
1018 }
1019
1020 /**
1021 * Add access rules to the given ACL #acl according to the constraints in the
1022 * particular access promise.
1023 *
1024 * For legacy reasons (non-TLS connections), build also the #ap (access Auth)
1025 * and #dp (deny Auth), if they are not NULL.
1026 */
AccessPromise_AddAccessConstraints(const EvalContext * ctx,const Promise * pp,struct resource_acl * racl,Auth * ap,Auth * dp)1027 static void AccessPromise_AddAccessConstraints(const EvalContext *ctx,
1028 const Promise *pp,
1029 struct resource_acl *racl,
1030 Auth *ap, Auth *dp)
1031 {
1032 for (size_t i = 0; i < SeqLength(pp->conlist); i++)
1033 {
1034 const Constraint *cp = SeqAt(pp->conlist, i);
1035 size_t ret = -2;
1036
1037 if (!IsDefinedClass(ctx, cp->classes))
1038 {
1039 continue;
1040 }
1041
1042 switch (cp->rval.type)
1043 {
1044 #define IsAccessBody(e) (strcmp(cp->lval, CF_REMACCESS_BODIES[e].lval) == 0)
1045
1046 case RVAL_TYPE_SCALAR:
1047
1048 if (ap != NULL &&
1049 IsAccessBody(REMOTE_ACCESS_IFENCRYPTED))
1050 {
1051 ap->encrypt = BooleanFromString(cp->rval.item);
1052 }
1053 else if (IsAccessBody(REMOTE_ACCESS_SHORTCUT))
1054 {
1055 const char *shortcut = cp->rval.item;
1056
1057 if (strchr(shortcut, FILE_SEPARATOR) != NULL)
1058 {
1059 Log(LOG_LEVEL_ERR,
1060 "slashes are forbidden in ACL shortcut: %s",
1061 shortcut);
1062 }
1063 else if (StringMapHasKey(SERVER_ACCESS.path_shortcuts, shortcut))
1064 {
1065 Log(LOG_LEVEL_WARNING,
1066 "Already existing shortcut for path '%s' was replaced",
1067 pp->promiser);
1068 }
1069 else
1070 {
1071 StringMapInsert(SERVER_ACCESS.path_shortcuts,
1072 xstrdup(shortcut), xstrdup(pp->promiser));
1073
1074 Log(LOG_LEVEL_DEBUG, "Added shortcut '%s' for path: %s",
1075 shortcut, pp->promiser);
1076 }
1077 }
1078 break;
1079
1080 case RVAL_TYPE_LIST:
1081
1082 for (const Rlist *rp = (const Rlist *) cp->rval.item;
1083 rp != NULL; rp = rp->next)
1084 {
1085 /* TODO keys, ips, hostnames are valid such strings. */
1086
1087 if (IsAccessBody(REMOTE_ACCESS_ADMITIPS))
1088 {
1089 ret = StrList_Append(&racl->admit.ips, RlistScalarValue(rp));
1090 if (ap != NULL)
1091 {
1092 PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL);
1093 }
1094 }
1095 else if (IsAccessBody(REMOTE_ACCESS_DENYIPS))
1096 {
1097 ret = StrList_Append(&racl->deny.ips, RlistScalarValue(rp));
1098 if (dp != NULL)
1099 {
1100 PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL);
1101 }
1102 }
1103 else if (IsAccessBody(REMOTE_ACCESS_ADMITHOSTNAMES))
1104 {
1105 ret = StrList_Append(&racl->admit.hostnames, RlistScalarValue(rp));
1106 /* If any hostname rule got added,
1107 * turn on reverse DNS lookup in the new protocol. */
1108 if (ret != (size_t) -1)
1109 {
1110 TurnOnReverseLookups();
1111 }
1112 if (ap != NULL)
1113 {
1114 NewHostToOldACL(ap, RlistScalarValue(rp));
1115 }
1116 }
1117 else if (IsAccessBody(REMOTE_ACCESS_DENYHOSTNAMES))
1118 {
1119 ret = StrList_Append(&racl->deny.hostnames, RlistScalarValue(rp));
1120 /* If any hostname rule got added,
1121 * turn on reverse DNS lookup in the new protocol. */
1122 if (ret != (size_t) -1)
1123 {
1124 TurnOnReverseLookups();
1125 }
1126 if (dp != NULL)
1127 {
1128 NewHostToOldACL(dp, RlistScalarValue(rp));
1129 }
1130 }
1131 else if (IsAccessBody(REMOTE_ACCESS_ADMITKEYS))
1132 {
1133 ret = StrList_Append(&racl->admit.keys, RlistScalarValue(rp));
1134 }
1135 else if (IsAccessBody(REMOTE_ACCESS_DENYKEYS))
1136 {
1137 ret = StrList_Append(&racl->deny.keys, RlistScalarValue(rp));
1138 }
1139 /* Legacy stuff */
1140 else if (IsAccessBody(REMOTE_ACCESS_ADMIT))
1141 {
1142 ret = racl_SmartAppend(&racl->admit, RlistScalarValue(rp));
1143 if (ap != NULL)
1144 {
1145 PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL);
1146 }
1147 }
1148 else if (IsAccessBody(REMOTE_ACCESS_DENY))
1149 {
1150 ret = racl_SmartAppend(&racl->deny, RlistScalarValue(rp));
1151 if (dp != NULL)
1152 {
1153 PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL);
1154 }
1155 }
1156 else if (ap != NULL && IsAccessBody(REMOTE_ACCESS_MAPROOT))
1157 {
1158 PrependItem(&(ap->maproot), RlistScalarValue(rp), NULL);
1159 }
1160 }
1161
1162 if (ret == (size_t) -1)
1163 {
1164 /* Should never happen, besides when allocation fails. */
1165 Log(LOG_LEVEL_CRIT, "StrList_Append: %s", GetErrorStr());
1166 DoCleanupAndExit(255);
1167 }
1168
1169 break;
1170
1171 default:
1172 UnexpectedError("Unknown constraint type!");
1173 break;
1174
1175 #undef IsAccessBody
1176 }
1177 }
1178
1179 StrList_Finalise(&racl->admit.ips);
1180 StrList_Sort(racl->admit.ips, string_Compare);
1181
1182 StrList_Finalise(&racl->admit.hostnames);
1183 StrList_Sort(racl->admit.hostnames, string_CompareFromEnd);
1184
1185 StrList_Finalise(&racl->admit.keys);
1186 StrList_Sort(racl->admit.keys, string_Compare);
1187
1188 StrList_Finalise(&racl->deny.ips);
1189 StrList_Sort(racl->deny.ips, string_Compare);
1190
1191 StrList_Finalise(&racl->deny.hostnames);
1192 StrList_Sort(racl->deny.hostnames, string_CompareFromEnd);
1193
1194 StrList_Finalise(&racl->deny.keys);
1195 StrList_Sort(racl->deny.keys, string_Compare);
1196 }
1197
1198 /* It is allowed to have duplicate handles (paths or class names or variables
1199 * etc) in bundle server access_rules in policy files, but the lists here
1200 * should have unique entries. This, we make sure here. */
GetOrCreateAuth(const char * handle,Auth ** authchain,Auth ** authchain_tail)1201 static Auth *GetOrCreateAuth(const char *handle, Auth **authchain, Auth **authchain_tail)
1202 {
1203 Auth *a = GetAuthPath(handle, *authchain);
1204
1205 if (!a)
1206 {
1207 InstallServerAuthPath(handle, authchain, authchain_tail);
1208 a = GetAuthPath(handle, *authchain);
1209 }
1210
1211 return a;
1212 }
1213
KeepFileAccessPromise(const EvalContext * ctx,const Promise * pp)1214 static void KeepFileAccessPromise(const EvalContext *ctx, const Promise *pp)
1215 {
1216 char path[PATH_MAX];
1217 size_t path_len = strlen(pp->promiser);
1218 if (path_len > sizeof(path) - 1)
1219 {
1220 goto err_too_long;
1221 }
1222 memcpy(path, pp->promiser, path_len + 1);
1223
1224 /* Resolve symlinks and canonicalise access_rules path. */
1225 size_t ret2 = PreprocessRequestPath(path, sizeof(path));
1226
1227 if (ret2 == (size_t) -1)
1228 {
1229 if (errno != ENOENT) /* something went wrong */
1230 {
1231 Log(LOG_LEVEL_ERR,
1232 "Failed to canonicalize path '%s' in access_rules, ignoring!",
1233 pp->promiser);
1234 return;
1235 }
1236 else /* file does not exist, it doesn't matter */
1237 {
1238 Log(LOG_LEVEL_INFO,
1239 "Path does not exist, it's added as-is in access rules: %s",
1240 path);
1241 Log(LOG_LEVEL_INFO,
1242 "WARNING: this means that (not) having a trailing slash defines if it's (not) a directory!");
1243 /* Legacy: convert trailing "/." to "/" */
1244 if (path_len >= 2 &&
1245 path[path_len - 1] == '.' &&
1246 path[path_len - 2] == '/')
1247 {
1248 path[path_len - 1] = '\0';
1249 path_len--;
1250 }
1251 }
1252 }
1253 else /* file exists, path canonicalised */
1254 {
1255 /* If it's a directory append trailing '/' */
1256 path_len = ret2;
1257 bool is_dir = IsDirReal(path);
1258 if (is_dir && path[path_len - 1] != FILE_SEPARATOR)
1259 {
1260 if (path_len + 2 > sizeof(path))
1261 {
1262 goto err_too_long;
1263 }
1264 PathAppendTrailingSlash(path, path_len);
1265 path_len++;
1266 }
1267 }
1268
1269 size_t pos = acl_SortedInsert(&paths_acl, path);
1270 if (pos == (size_t) -1)
1271 {
1272 /* Should never happen, besides when allocation fails. */
1273 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1274 DoCleanupAndExit(255);
1275 }
1276
1277 /* Legacy code */
1278 if (path_len != 1)
1279 {
1280 DeleteSlash(path);
1281 }
1282 Auth *ap = GetOrCreateAuth(path, &SERVER_ACCESS.admit, &SERVER_ACCESS.admittail);
1283 Auth *dp = GetOrCreateAuth(path, &SERVER_ACCESS.deny, &SERVER_ACCESS.denytail);
1284
1285 AccessPromise_AddAccessConstraints(ctx, pp, &paths_acl->acls[pos],
1286 ap, dp);
1287 return;
1288
1289 err_too_long:
1290 Log(LOG_LEVEL_ERR,
1291 "Path '%s' in access_rules is too long (%zu > %d), ignoring!",
1292 pp->promiser, strlen(pp->promiser), PATH_MAX);
1293 return;
1294 }
1295
1296 /*********************************************************************/
1297
KeepLiteralAccessPromise(EvalContext * ctx,const Promise * pp,const char * type)1298 void KeepLiteralAccessPromise(EvalContext *ctx, const Promise *pp, const char *type)
1299 {
1300 Auth *ap, *dp;
1301 const char *handle = PromiseGetHandle(pp);
1302
1303 if (handle == NULL && strcmp(type, "literal") == 0)
1304 {
1305 Log(LOG_LEVEL_ERR, "Access to literal server data requires you to define a promise handle for reference");
1306 return;
1307 }
1308
1309 if (strcmp(type, "literal") == 0)
1310 {
1311 Log(LOG_LEVEL_VERBOSE,"Looking at literal access promise '%s', type '%s'", pp->promiser, type);
1312
1313 ap = GetOrCreateAuth(handle, &SERVER_ACCESS.varadmit, &SERVER_ACCESS.varadmittail);
1314 dp = GetOrCreateAuth(handle, &SERVER_ACCESS.vardeny, &SERVER_ACCESS.vardenytail);
1315
1316 RegisterLiteralServerData(ctx, handle, pp);
1317 ap->literal = true;
1318
1319
1320 size_t pos = acl_SortedInsert(&literals_acl, handle);
1321 if (pos == (size_t) -1)
1322 {
1323 /* Should never happen, besides when allocation fails. */
1324 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1325 DoCleanupAndExit(255);
1326 }
1327
1328 AccessPromise_AddAccessConstraints(ctx, pp, &literals_acl->acls[pos],
1329 ap, dp);
1330 }
1331 else
1332 {
1333 Log(LOG_LEVEL_VERBOSE,"Looking at context/var access promise '%s', type '%s'", pp->promiser, type);
1334
1335 ap = GetOrCreateAuth(pp->promiser, &SERVER_ACCESS.varadmit, &SERVER_ACCESS.varadmittail);
1336 dp = GetOrCreateAuth(pp->promiser, &SERVER_ACCESS.vardeny, &SERVER_ACCESS.vardenytail);
1337
1338 if (strcmp(type, "context") == 0)
1339 {
1340 ap->classpattern = true;
1341
1342 size_t pos = acl_SortedInsert(&classes_acl, pp->promiser);
1343 if (pos == (size_t) -1)
1344 {
1345 /* Should never happen, besides when allocation fails. */
1346 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1347 DoCleanupAndExit(255);
1348 }
1349
1350 AccessPromise_AddAccessConstraints(ctx, pp, &classes_acl->acls[pos],
1351 ap, dp);
1352 }
1353 else if (strcmp(type, "variable") == 0)
1354 {
1355 ap->variable = true;
1356
1357 size_t pos = acl_SortedInsert(&vars_acl, pp->promiser);
1358 if (pos == (size_t) -1)
1359 {
1360 /* Should never happen, besides when allocation fails. */
1361 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1362 DoCleanupAndExit(255);
1363 }
1364
1365 AccessPromise_AddAccessConstraints(ctx, pp, &vars_acl->acls[pos],
1366 ap, dp);
1367 }
1368 }
1369 }
1370
1371 /*********************************************************************/
1372
KeepQueryAccessPromise(EvalContext * ctx,const Promise * pp)1373 static void KeepQueryAccessPromise(EvalContext *ctx, const Promise *pp)
1374 {
1375 Auth *dp = GetOrCreateAuth(pp->promiser, &SERVER_ACCESS.vardeny, &SERVER_ACCESS.vardenytail),
1376 *ap = GetOrCreateAuth(pp->promiser, &SERVER_ACCESS.varadmit, &SERVER_ACCESS.varadmittail);
1377
1378 RegisterLiteralServerData(ctx, pp->promiser, pp);
1379 ap->literal = true;
1380
1381 size_t pos = acl_SortedInsert(&query_acl, pp->promiser);
1382 if (pos == (size_t) -1)
1383 {
1384 /* Should never happen, besides when allocation fails. */
1385 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1386 DoCleanupAndExit(255);
1387 }
1388
1389 AccessPromise_AddAccessConstraints(ctx, pp, &query_acl->acls[pos],
1390 ap, dp);
1391 }
1392
KeepBundlesAccessPromise(EvalContext * ctx,const Promise * pp)1393 static void KeepBundlesAccessPromise(EvalContext *ctx, const Promise *pp)
1394 {
1395 size_t pos = acl_SortedInsert(&bundles_acl, pp->promiser);
1396 if (pos == (size_t) -1)
1397 {
1398 /* Should never happen, besides when allocation fails. */
1399 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1400 DoCleanupAndExit(255);
1401 }
1402
1403 /* Last params are NULL because we don't have
1404 * old-school Auth type ACLs here. */
1405 AccessPromise_AddAccessConstraints(ctx, pp, &bundles_acl->acls[pos],
1406 NULL, NULL);
1407 }
1408 /*********************************************************************/
1409
1410 /**
1411 * The "roles" access promise is for remote class activation by means of
1412 * cf-runagent -D:
1413 *
1414 * pp->promiser is a regex to match classes.
1415 * pp->conlist is an slist of usernames.
1416 */
KeepServerRolePromise(EvalContext * ctx,const Promise * pp)1417 static void KeepServerRolePromise(EvalContext *ctx, const Promise *pp)
1418 {
1419 size_t pos = acl_SortedInsert(&roles_acl, pp->promiser);
1420 if (pos == (size_t) -1)
1421 {
1422 /* Should never happen, besides when allocation fails. */
1423 Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr());
1424 DoCleanupAndExit(255);
1425 }
1426
1427 size_t i = SeqLength(pp->conlist);
1428 while (i > 0)
1429 {
1430 i--;
1431 Constraint *cp = SeqAt(pp->conlist, i);
1432 char const * const authorizer =
1433 CF_REMROLE_BODIES[REMOTE_ROLE_AUTHORIZE].lval;
1434
1435 if (strcmp(cp->lval, authorizer) == 0)
1436 {
1437 if (cp->rval.type != RVAL_TYPE_LIST)
1438 {
1439 Log(LOG_LEVEL_ERR,
1440 "Right-hand side of authorize promise for '%s' should be a list",
1441 pp->promiser);
1442 }
1443 else if (IsDefinedClass(ctx, cp->classes))
1444 {
1445 for (const Rlist *rp = cp->rval.item; rp != NULL; rp = rp->next)
1446 {
1447 /* The "roles" access promise currently only supports
1448 * listing usernames to admit access to, nothing more. */
1449 struct resource_acl *racl = &roles_acl->acls[pos];
1450 size_t zret = StrList_Append(&racl->admit.usernames,
1451 RlistScalarValue(rp));
1452 if (zret == (size_t) -1)
1453 {
1454 /* Should never happen, besides when allocation fails. */
1455 Log(LOG_LEVEL_CRIT, "StrList_Append: %s", GetErrorStr());
1456 DoCleanupAndExit(255);
1457 }
1458 }
1459 }
1460 }
1461 else if (strcmp(cp->lval, "comment") != 0 &&
1462 strcmp(cp->lval, "handle") != 0 &&
1463 /* Are there other known list constraints ? if not, skip this: */
1464 cp->rval.type != RVAL_TYPE_LIST)
1465 {
1466 Log(LOG_LEVEL_WARNING,
1467 "Unrecognised promise '%s' for %s",
1468 cp->lval, pp->promiser);
1469 }
1470 }
1471 }
1472
InstallServerAuthPath(const char * path,Auth ** list,Auth ** listtail)1473 static void InstallServerAuthPath(const char *path, Auth **list, Auth **listtail)
1474 {
1475 Auth **nextp = *listtail ? &((*listtail)->next) : list;
1476 assert(*nextp == NULL);
1477 *listtail = *nextp = xcalloc(1, sizeof(Auth));
1478 (*nextp)->path = xstrdup(path);
1479
1480 #ifdef __MINGW32__
1481 for (char *p = (*nextp)->path; *p != '\0'; p++)
1482 {
1483 *p = ToLower(*p);
1484 }
1485 #endif /* __MINGW32__ */
1486 }
1487
GetAuthPath(const char * path,Auth * list)1488 static Auth *GetAuthPath(const char *path, Auth *list)
1489 {
1490 size_t path_len = strlen(path);
1491 char unslashed_path[path_len + 1];
1492 memcpy(unslashed_path, path, path_len + 1);
1493
1494 #ifdef __MINGW32__
1495 ToLowerStrInplace(unslashed_path);
1496 #endif
1497
1498 if (path_len != 1)
1499 {
1500 DeleteSlash(unslashed_path);
1501 }
1502
1503 for (Auth *ap = list; ap != NULL; ap = ap->next)
1504 {
1505 if (strcmp(ap->path, unslashed_path) == 0)
1506 {
1507 return ap;
1508 }
1509 }
1510
1511 return NULL;
1512 }
1513