/* Only search in PATH_STRLIST because it makes sense to search longest prefix * for paths. */ static void test_StrList_SearchLongestPrefix() { /* REMINDER: PATH_STRINGS[] = { " ", " ", "/", "/path", "/path/to/file.name", "blah", "waza" }; */ size_t ret, ret2, ret3; /* These searches all search for "/path", since length is the same. */ ret = StrList_SearchLongestPrefix(PATH_SL, "/path", 0, '/', true); ret2 = StrList_SearchLongestPrefix(PATH_SL, "/path/", 5, '/', true); ret3 = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name", 5, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/path"); assert_string_equal(StrList_At(PATH_SL, ret2), "/path"); assert_string_equal(StrList_At(PATH_SL, ret3), "/path"); /* Searching for "/path/" does not bring up "/path", but "/", since * directories *must* have a trailing slash. */ ret = StrList_SearchLongestPrefix(PATH_SL, "/path/", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/"); ret = StrList_SearchLongestPrefix(PATH_SL, "/path.json", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/"); /* We insert a couple more directories and sort again. */ StrList_Append(&PATH_SL, "/path/to/file.namewhatever/whatever"); StrList_Append(&PATH_SL, "/path/to/file.name/whatever/"); StrList_Append(&PATH_SL, "/path/to/"); StrList_Sort(PATH_SL, string_Compare); ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name"); ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/"); ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/file.name/whatever/blah", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/file.name/whatever/"); ret = StrList_SearchLongestPrefix(PATH_SL, "/path/to/", 0, '/', true); assert_string_equal(StrList_At(PATH_SL, ret), "/path/to/"); }
/** * The "roles" access promise is for remote class activation by means of * cf-runagent -D: * * pp->promiser is a regex to match classes. * pp->conlist is an slist of usernames. */ static void KeepServerRolePromise(EvalContext *ctx, const Promise *pp) { size_t pos = acl_SortedInsert(&roles_acl, pp->promiser); if (pos == (size_t) -1) { /* Should never happen, besides when allocation fails. */ Log(LOG_LEVEL_CRIT, "acl_Insert: %s", GetErrorStr()); exit(255); } size_t i = SeqLength(pp->conlist); while (i > 0) { i--; Constraint *cp = SeqAt(pp->conlist, i); char const * const authorizer = CF_REMROLE_BODIES[REMOTE_ROLE_AUTHORIZE].lval; if (strcmp(cp->lval, authorizer) == 0) { if (cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_ERR, "Right-hand side of authorize promise for '%s' should be a list", pp->promiser); } else if (IsDefinedClass(ctx, cp->classes)) { for (const Rlist *rp = cp->rval.item; rp != NULL; rp = rp->next) { /* The "roles" access promise currently only supports * listing usernames to admit access to, nothing more. */ struct resource_acl *racl = &roles_acl->acls[pos]; size_t zret = StrList_Append(&racl->admit.usernames, RlistScalarValue(rp)); if (zret == (size_t) -1) { /* Should never happen, besides when allocation fails. */ Log(LOG_LEVEL_CRIT, "StrList_Append: %s", GetErrorStr()); exit(255); } } } } else if (strcmp(cp->lval, "comment") != 0 && strcmp(cp->lval, "handle") != 0 && /* Are there other known list constraints ? if not, skip this: */ cp->rval.type != RVAL_TYPE_LIST) { Log(LOG_LEVEL_WARNING, "Unrecognised promise '%s' for %s", cp->lval, pp->promiser); } } }
static size_t racl_SmartAppend(struct admitdeny_acl *ad, const char *entry) { size_t ret; switch (AdmitType(entry)) { case ADMIT_TYPE_IP: /* TODO convert IP string to binary representation. */ ret = StrList_Append(&ad->ips, entry); break; case ADMIT_TYPE_KEY: ret = StrList_Append(&ad->keys, entry); break; case ADMIT_TYPE_HOSTNAME: /* TODO clean up possible regex, if it starts with ".*" * then store two entries: entry, and *dot*entry. */ ret = StrList_Append(&ad->hostnames, entry); /* If any hostname rule is present, we set a global flag to turn on * reverse DNS lookup in the new protocol. */ if (!NEED_REVERSE_LOOKUP) { Log(LOG_LEVEL_INFO, "Found hostname admit/deny access_rules, " "turning on reverse DNS lookups on every connection"); NEED_REVERSE_LOOKUP = true; } break; default: Log(LOG_LEVEL_WARNING, "Access rule 'admit: %s' is not IP, hostname or key, ignoring", entry); ret = (size_t) -1; } return ret; }
static size_t racl_SmartAppend(struct admitdeny_acl *ad, const char *entry) { size_t ret; switch (AdmitType(entry)) { case ADMIT_TYPE_IP: /* TODO convert IP string to binary representation. */ ret = StrList_Append(&ad->ips, entry); break; case ADMIT_TYPE_KEY: ret = StrList_Append(&ad->keys, entry); break; case ADMIT_TYPE_HOSTNAME: ret = DeRegexify(&ad->hostnames, entry); /* If any hostname rule got added, * turn on reverse DNS lookup in the new protocol. */ if (ret != (size_t) -1) { TurnOnReverseLookups(); } break; default: Log(LOG_LEVEL_WARNING, "Access rule 'admit: %s' is not IP, hostname or key, ignoring", entry); ret = (size_t) -1; } return ret; }
static StrList *init_strlist(char ** strings, size_t strings_len, size_t *insert_order) { StrList *sl = calloc(1, sizeof(*sl)); /* Insert in random order. */ for (size_t i = 0; i < strings_len; i++) { size_t ret = StrList_Append(&sl, strings[ insert_order[i] ]); assert_int_equal(ret, i); assert_string_equal(StrList_At(sl, i), strings[ insert_order[i] ]); } assert_int_equal(StrList_Len(sl), strings_len); StrList_Finalise(&sl); return sl; }
/** * Add access rules to the given ACL #acl according to the constraints in the * particular access promise. * * For legacy reasons (non-TLS connections), build also the #ap (access Auth) * and #dp (deny Auth). */ static void AccessPromise_AddAccessConstraints(const EvalContext *ctx, const Promise *pp, struct resource_acl *racl, Auth *ap, Auth *dp) { for (size_t i = 0; i < SeqLength(pp->conlist); i++) { const Constraint *cp = SeqAt(pp->conlist, i); size_t ret = -2; if (!IsDefinedClass(ctx, cp->classes)) { continue; } switch (cp->rval.type) { #define IsAccessBody(e) (strcmp(cp->lval, CF_REMACCESS_BODIES[e].lval) == 0) case RVAL_TYPE_SCALAR: if (IsAccessBody(REMOTE_ACCESS_IFENCRYPTED)) { ap->encrypt = BooleanFromString(cp->rval.item); } else if (IsAccessBody(REMOTE_ACCESS_SHORTCUT)) { const char *shortcut = cp->rval.item; if (strchr(shortcut, FILE_SEPARATOR) != NULL) { Log(LOG_LEVEL_ERR, "slashes are forbidden in ACL shortcut: %s", shortcut); } else if (StringMapHasKey(SV.path_shortcuts, shortcut)) { Log(LOG_LEVEL_WARNING, "Already existing shortcut for path '%s' was replaced", pp->promiser); } else { StringMapInsert(SV.path_shortcuts, xstrdup(shortcut), xstrdup(pp->promiser)); Log(LOG_LEVEL_DEBUG, "Added shortcut '%s' for path: %s", shortcut, pp->promiser); } } break; case RVAL_TYPE_LIST: for (const Rlist *rp = (const Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { /* TODO keys, ips, hostnames are valid such strings. */ if (IsAccessBody(REMOTE_ACCESS_ADMITIPS)) { ret = StrList_Append(&racl->admit.ips, RlistScalarValue(rp)); PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL); } else if (IsAccessBody(REMOTE_ACCESS_DENYIPS)) { ret = StrList_Append(&racl->deny.ips, RlistScalarValue(rp)); PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL); } else if (IsAccessBody(REMOTE_ACCESS_ADMITHOSTNAMES)) { ret = StrList_Append(&racl->admit.hostnames, RlistScalarValue(rp)); /* If any hostname rule got added, * turn on reverse DNS lookup in the new protocol. */ if (ret != (size_t) -1) { TurnOnReverseLookups(); } NewHostToOldACL(ap, RlistScalarValue(rp)); } else if (IsAccessBody(REMOTE_ACCESS_DENYHOSTNAMES)) { ret = StrList_Append(&racl->deny.hostnames, RlistScalarValue(rp)); /* If any hostname rule got added, * turn on reverse DNS lookup in the new protocol. */ if (ret != (size_t) -1) { TurnOnReverseLookups(); } NewHostToOldACL(dp, RlistScalarValue(rp)); } else if (IsAccessBody(REMOTE_ACCESS_ADMITKEYS)) { ret = StrList_Append(&racl->admit.keys, RlistScalarValue(rp)); } else if (IsAccessBody(REMOTE_ACCESS_DENYKEYS)) { ret = StrList_Append(&racl->deny.keys, RlistScalarValue(rp)); } /* Legacy stuff */ else if (IsAccessBody(REMOTE_ACCESS_ADMIT)) { ret = racl_SmartAppend(&racl->admit, RlistScalarValue(rp)); PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL); } else if (IsAccessBody(REMOTE_ACCESS_DENY)) { ret = racl_SmartAppend(&racl->deny, RlistScalarValue(rp)); PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL); } else if (IsAccessBody(REMOTE_ACCESS_MAPROOT)) { PrependItem(&(ap->maproot), RlistScalarValue(rp), NULL); } } if (ret == (size_t) -1) { /* Should never happen, besides when allocation fails. */ Log(LOG_LEVEL_CRIT, "StrList_Append: %s", GetErrorStr()); exit(255); } break; default: UnexpectedError("Unknown constraint type!"); break; #undef IsAccessBody } } StrList_Finalise(&racl->admit.ips); StrList_Sort(racl->admit.ips, string_Compare); StrList_Finalise(&racl->admit.hostnames); StrList_Sort(racl->admit.hostnames, string_CompareFromEnd); StrList_Finalise(&racl->admit.keys); StrList_Sort(racl->admit.keys, string_Compare); StrList_Finalise(&racl->deny.ips); StrList_Sort(racl->deny.ips, string_Compare); StrList_Finalise(&racl->deny.hostnames); StrList_Sort(racl->deny.hostnames, string_CompareFromEnd); StrList_Finalise(&racl->deny.keys); StrList_Sort(racl->deny.keys, string_Compare); }
/* Map old-style regex-or-hostname to new-style host-or-domain. * * Old-style ACLs could include regexes to be matched against host * names; but new-style ones only support sub-domain matching. If the * old-style host regex looks like ".*\.sub\.domain\.tld" we can take * it in as ".sub.domain.tld"; otherwise, we can only really map exact * match hostnames. However, we know some old policy (including our * own masterfiles) had cases of .*sub.domain.tld and it's possible * that someone might include a new-style .sub.domain.tld by mistake * in an (old-style) accept list; so cope with these cases, too. * * @param sl The string-list to which to add entries. * @param host The name-or-regex to add to the ACL. * @return An index at which an entry was added to the list (there may * be another), or -1 if nothing added. */ static size_t DeRegexify(StrList **sl, const char *host) { if (IsRegex(host)) { if (host[strcspn(host, "({[|+?]})")] != '\0') { return -1; /* Not a regex we can sensibly massage; discard. */ } bool skip[2] = { false, false }; /* { domain, host } passes below */ const char *name = host; if (name[0] == '^') /* Was always implicit; but read as hint to intent. */ { /* Default to skipping domain-form if anchored explicitly: */ skip[0] = true; /* Over-ridden below if followed by .* of course. */ name++; } if (StringStartsWith(name, ".*")) { skip[0] = false; /* Domain-form should match */ name += 2; } if (StringStartsWith(name, "\\.")) { /* Skip host-form, as the regex definitely wants something * before the given name. */ skip[1] = true; name += 2; } if (strchr(name, '*') != NULL) { /* Can't handle a * later than the preamble. */ return (size_t) -1; } if (name > host || NULL != strchr(host, '\\')) { /* 2: leading '.' and final '\0' */ char copy[2 + strlen(name)], *c = copy; c++[0] = '.'; /* For domain-form; and copy+1 gives host-form. */ /* Now copy the rest of the name, de-regex-ifying as we go: */ for (const char *p = name; p[0] != '\0'; p++) { if (p[0] == '\\') { p++; if (p[0] != '.') { /* Regex includes a non-dot escape */ return (size_t) -1; } } #if 0 else if (p[0] == '.') { /* In principle, this is a special character; but * it may just be an unescaped dot, so let it be. */ } #endif c++[0] = p[0]; } assert(c < copy + sizeof(copy)); c[0] = '\0'; /* Now, for host then domain, add entry if suitable */ int pass = 2; size_t ret = -1; while (pass > 0) { pass--; if (!skip[pass]) /* pass 0 is domain, pass 1 is host */ { ret = StrList_Append(sl, copy + pass); } } return ret; } /* IsRegex() but is actually so boring it's just a name ! */ } /* Just a simple host name. */ return StrList_Append(sl, host); }