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