int CopyACLs(const char *src, const char *dst) { acl_t acls; struct stat statbuf; int ret; acls = acl_get_file(src, ACL_TYPE_ACCESS); if (!acls) { if (errno == ENOTSUP) { return true; } else { Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (acl_get_file: %s)", src, GetErrorStr()); return false; } } ret = acl_set_file(dst, ACL_TYPE_ACCESS, acls); acl_free(acls); if (ret != 0) { if (errno == ENOTSUP) { return true; } else { Log(LOG_LEVEL_ERR, "Can't copy ACLs to '%s'. (acl_set_file: %s)", dst, GetErrorStr()); return false; } } if (stat(src, &statbuf) != 0) { Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (stat: %s)", src, GetErrorStr()); return false; } if (!S_ISDIR(statbuf.st_mode)) { return true; } // For directory, copy default ACL too. acls = acl_get_file(src, ACL_TYPE_DEFAULT); if (!acls) { Log(LOG_LEVEL_ERR, "Can't copy ACLs from '%s'. (acl_get_file: %s)", src, GetErrorStr()); return false; } ret = acl_set_file(dst, ACL_TYPE_DEFAULT, acls); acl_free(acls); if (ret != 0) { Log(LOG_LEVEL_ERR, "Can't copy ACLs to '%s'. (acl_set_file: %s)", dst, GetErrorStr()); return false; } return true; }
int CheckDefaultClearACL(EvalContext *ctx, const char *file_path, Attributes a, const Promise *pp, PromiseResult *result) { acl_t acl_existing; acl_t acl_empty; acl_entry_t ace_dummy; int retv; int retval = false; acl_existing = NULL; acl_empty = NULL; if ((acl_existing = acl_get_file(file_path, ACL_TYPE_DEFAULT)) == NULL) { Log(LOG_LEVEL_ERR, "Unable to read default acl for '%s'. (acl_get_file: %s)", file_path, GetErrorStr()); return false; } retv = acl_get_entry(acl_existing, ACL_FIRST_ENTRY, &ace_dummy); switch (retv) { case -1: Log(LOG_LEVEL_VERBOSE, "Couldn't retrieve ACE for '%s'. (acl_get_entry: %s)", file_path, GetErrorStr()); retval = false; break; case 0: // no entries, as desired cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Default ACL on '%s' needs no modification.", file_path); retval = true; break; case 1: // entries exist, set empty ACL if ((acl_empty = acl_init(0)) == NULL) { Log(LOG_LEVEL_ERR, "Could not reinitialize ACL for '%s'. (acl_init: %s)", file_path, GetErrorStr()); retval = false; break; } switch (a.transaction.action) { case cfa_warn: cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a, "Default ACL on '%s' needs to be cleared", file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); break; case cfa_fix: if (!DONTDO) { if (acl_set_file(file_path, ACL_TYPE_DEFAULT, acl_empty) != 0) { Log(LOG_LEVEL_ERR, "Could not reset ACL for %s", file_path); retval = false; break; } } cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Default ACL on '%s' successfully cleared", file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); retval = true; break; default: ProgrammingError("CFEngine: internal error: illegal file action"); retval = false; } break; default: retval = false; } acl_free(acl_empty); acl_free(acl_existing); return retval; }
static int ACLEquals(acl_t first, acl_t second) { acl_entry_t ace_first; acl_entry_t ace_second; acl_permset_t perms_first; acl_permset_t perms_second; int first_cnt; int second_cnt; int more_aces; int retv_perms; if ((first_cnt = ACECount(first)) == -1) { Log(LOG_LEVEL_VERBOSE, "Couldn't count ACEs"); return -1; } if ((second_cnt = ACECount(second)) == -1) { Log(LOG_LEVEL_VERBOSE, "Couldn't count ACEs"); return -1; } if (first_cnt != second_cnt) { return 1; } if (first_cnt == 0) { return 0; } // check that every ace of first acl exist in second acl more_aces = acl_get_entry(first, ACL_FIRST_ENTRY, &ace_first); if (more_aces != 1) // first must contain at least one entry { Log(LOG_LEVEL_ERR, "Unable to read ACE. (acl_get_entry: %s)", GetErrorStr()); return -1; } while (more_aces) { /* no ace in second match entity-type and id of first */ if ((ace_second = FindACE(second, ace_first)) == NULL) { return 1; } /* permissions must also match */ if (acl_get_permset(ace_first, &perms_first) != 0) { Log(LOG_LEVEL_ERR, "Unable to read permissions. (acl_get_permset: %s)", GetErrorStr()); return -1; } if (acl_get_permset(ace_second, &perms_second) != 0) { Log(LOG_LEVEL_ERR, "Unable to read permissions. (acl_get_permset: %s)", GetErrorStr()); return -1; } retv_perms = PermsetEquals(perms_first, perms_second); if (retv_perms == -1) { return -1; } else if (retv_perms == 1) // permissions differ { return 1; } more_aces = acl_get_entry(first, ACL_NEXT_ENTRY, &ace_first); } return 0; }
static void TransformGidsToGroups(StringSet **list) { StringSet *new_list = StringSetNew(); StringSetIterator i = StringSetIteratorInit(*list); const char *data; for (data = StringSetIteratorNext(&i); data; data = StringSetIteratorNext(&i)) { if (strlen(data) != strspn(data, "0123456789")) { // Cannot possibly be a gid. StringSetAdd(new_list, xstrdup(data)); continue; } // In groups vs gids, groups take precedence. So check if it exists. errno = 0; struct group *group_info = getgrnam(data); if (!group_info) { switch (errno) { case 0: case ENOENT: case EBADF: case ESRCH: case EWOULDBLOCK: case EPERM: // POSIX is apparently ambiguous here. All values mean "not found". errno = 0; group_info = getgrgid(atoi(data)); if (!group_info) { switch (errno) { case 0: case ENOENT: case EBADF: case ESRCH: case EWOULDBLOCK: case EPERM: // POSIX is apparently ambiguous here. All values mean "not found". // // Neither group nor gid is found. This will lead to an error later, but we don't // handle that here. break; default: Log(LOG_LEVEL_ERR, "Error while checking group name '%s'. (getgrgid: '%s')", data, GetErrorStr()); StringSetDestroy(new_list); return; } } else { // Replace gid with group name. StringSetAdd(new_list, xstrdup(group_info->gr_name)); } break; default: Log(LOG_LEVEL_ERR, "Error while checking group name '%s'. (getgrnam: '%s')", data, GetErrorStr()); StringSetDestroy(new_list); return; } } else { StringSetAdd(new_list, xstrdup(data)); } } StringSet *old_list = *list; *list = new_list; StringSetDestroy(old_list); }
static int CheckPosixLinuxACEs(EvalContext *ctx, Rlist *aces, AclMethod method, const char *file_path, acl_type_t acl_type, Attributes a, const Promise *pp, PromiseResult *result) { acl_t acl_existing; acl_t acl_new; acl_t acl_tmp; acl_entry_t ace_parsed; acl_entry_t ace_current; acl_permset_t perms; char *cf_ace; int retv; int has_mask; Rlist *rp; char *acl_type_str; acl_new = NULL; acl_existing = NULL; acl_tmp = NULL; has_mask = false; acl_type_str = acl_type == ACL_TYPE_ACCESS ? "Access" : "Default"; // read existing acl if ((acl_existing = acl_get_file(file_path, acl_type)) == NULL) { Log(LOG_LEVEL_VERBOSE, "No ACL for '%s' could be read. (acl_get_file: %s)", file_path, GetErrorStr()); return false; } // allocate memory for temp ace (it needs to reside in a temp acl) if ((acl_tmp = acl_init(1)) == NULL) { Log(LOG_LEVEL_ERR, "New ACL could not be allocated (acl_init: %s)", GetErrorStr()); acl_free((void *) acl_existing); return false; } if (acl_create_entry(&acl_tmp, &ace_parsed) != 0) { Log(LOG_LEVEL_ERR, "New ACL could not be allocated (acl_create_entry: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); return false; } // copy existing aces if we are appending if (method == ACL_METHOD_APPEND) { if ((acl_new = acl_dup(acl_existing)) == NULL) { Log(LOG_LEVEL_ERR, "Error copying existing ACL (acl_dup: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); return false; } } else // overwrite existing acl { if ((acl_new = acl_init(5)) == NULL) // TODO: Always OK with 5 here ? { Log(LOG_LEVEL_ERR, "New ACL could not be allocated (acl_init: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); return false; } } for (rp = aces; rp != NULL; rp = rp->next) { cf_ace = RlistScalarValue(rp); if (!ParseEntityPosixLinux(&cf_ace, ace_parsed, &has_mask)) { Log(LOG_LEVEL_ERR, "Error parsing entity in 'cf_ace'."); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } // check if an ACE with this entity-type and id already exist in the Posix Linux ACL ace_current = FindACE(acl_new, ace_parsed); // create new entry in ACL if it did not exist if (ace_current == NULL) { if (acl_create_entry(&acl_new, &ace_current) != 0) { Log(LOG_LEVEL_ERR, "Failed to allocate ace (acl_create_entry: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } // copy parsed entity-type and id if (acl_copy_entry(ace_current, ace_parsed) != 0) { Log(LOG_LEVEL_ERR, "Error copying Linux entry in 'cf_ace' (acl_copy_entry: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } // clear ace_current's permissions to avoid ace_parsed from last // loop iteration to be taken into account when applying mode below if ((acl_get_permset(ace_current, &perms) != 0)) { Log(LOG_LEVEL_ERR, "Error obtaining permset for 'ace_current' (acl_get_permset: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } if (acl_clear_perms(perms) != 0) { Log(LOG_LEVEL_ERR, "Error clearing permset for 'ace_current'. (acl_clear_perms: %s)", GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } } // mode string should be prefixed with an entry seperator if (*cf_ace != ':') { Log(LOG_LEVEL_ERR, "No separator before mode-string in 'cf_ace'"); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } cf_ace += 1; if (acl_get_permset(ace_current, &perms) != 0) { Log(LOG_LEVEL_ERR, "Error obtaining permset for 'cf_ace'"); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } if (!ParseModePosixLinux(cf_ace, perms)) { Log(LOG_LEVEL_ERR, "Error parsing mode-string in 'cf_ace'"); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } // only allow permissions exist on posix acls, so we do // not check what follows next } // if no mask exists, calculate one (or both?): run acl_calc_mask and add one if (!has_mask) { if (acl_calc_mask(&acl_new) != 0) { Log(LOG_LEVEL_ERR, "Error calculating new acl mask"); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } } if ((retv = ACLEquals(acl_existing, acl_new)) == -1) { Log(LOG_LEVEL_ERR, "Error while comparing existing and new ACL, unable to repair."); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } if (retv == 1) // existing and new acl differ, update existing { switch (a.transaction.action) { case cfa_warn: cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_WARN, pp, a, "%s ACL on file '%s' needs to be updated", acl_type_str, file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); break; case cfa_fix: if (!DONTDO) { if ((retv = acl_set_file(file_path, acl_type, acl_new)) != 0) { Log(LOG_LEVEL_ERR, "Error setting new %s ACL on file '%s' (acl_set_file: %s), are required ACEs present ?", acl_type_str, file_path, GetErrorStr()); acl_free((void *) acl_existing); acl_free((void *) acl_tmp); acl_free((void *) acl_new); return false; } } cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "%s ACL on '%s' successfully changed.", acl_type_str, file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); break; default: ProgrammingError("CFEngine: internal error: illegal file action"); } } else { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "'%s' ACL on '%s' needs no modification.", acl_type_str, file_path); } acl_free((void *) acl_existing); acl_free((void *) acl_new); acl_free((void *) acl_tmp); return true; }
static bool GetPasswordHash(const char *puser, const struct passwd *passwd_info, const char **result) { // Silence warning. (void)puser; #ifdef HAVE_GETSPNAM // If the hash is very short, it's probably a stub. Try getting the shadow password instead. if (strlen(passwd_info->pw_passwd) <= 4) { Log(LOG_LEVEL_VERBOSE, "Getting user '%s' password hash from shadow database.", puser); struct spwd *spwd_info; errno = 0; spwd_info = getspnam(puser); if (!spwd_info) { if (errno) { Log(LOG_LEVEL_ERR, "Could not get information from user shadow database. (getspnam: '%s')", GetErrorStr()); return false; } else { Log(LOG_LEVEL_ERR, "Could not find user when checking password."); return false; } } else if (spwd_info) { *result = spwd_info->sp_pwdp; return true; } } #endif // HAVE_GETSPNAM Log(LOG_LEVEL_VERBOSE, "Getting user '%s' password hash from passwd database.", puser); *result = passwd_info->pw_passwd; return true; }
static bool ChangePasswordHashUsingLckpwdf(const char *puser, const char *password) { bool result = false; struct stat statbuf; const char *passwd_file = "/etc/shadow"; if (stat(passwd_file, &statbuf) == -1) { passwd_file = "/etc/passwd"; } Log(LOG_LEVEL_VERBOSE, "Changing password hash for user '%s' by editing '%s'.", puser, passwd_file); if (lckpwdf() != 0) { Log(LOG_LEVEL_ERR, "Not able to obtain lock on password database."); return false; } char backup_file[strlen(passwd_file) + strlen(".cf-backup") + 1]; snprintf(backup_file, sizeof(backup_file), "%s.cf-backup", passwd_file); unlink(backup_file); char edit_file[strlen(passwd_file) + strlen(".cf-edit") + 1]; snprintf(edit_file, sizeof(edit_file), "%s.cf-edit", passwd_file); unlink(edit_file); if (!CopyRegularFileDisk(passwd_file, backup_file)) { Log(LOG_LEVEL_ERR, "Could not back up existing password database '%s' to '%s'.", passwd_file, backup_file); goto unlock_passwd; } FILE *passwd_fd = fopen(passwd_file, "r"); if (!passwd_fd) { Log(LOG_LEVEL_ERR, "Could not open password database '%s'. (fopen: '%s')", passwd_file, GetErrorStr()); goto unlock_passwd; } int edit_fd_int = open(edit_file, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR); if (edit_fd_int < 0) { if (errno == EEXIST) { Log(LOG_LEVEL_CRIT, "Temporary file already existed when trying to open '%s'. (open: '%s') " "This should NEVER happen and could mean that someone is trying to break into your system!!", edit_file, GetErrorStr()); } else { Log(LOG_LEVEL_ERR, "Could not open password database temporary file '%s'. (open: '%s')", edit_file, GetErrorStr()); } goto close_passwd_fd; } FILE *edit_fd = fdopen(edit_fd_int, "w"); if (!edit_fd) { Log(LOG_LEVEL_ERR, "Could not open password database temporary file '%s'. (fopen: '%s')", edit_file, GetErrorStr()); close(edit_fd_int); goto close_passwd_fd; } while (true) { size_t line_size = CF_BUFSIZE; char *line = xmalloc(line_size); int read_result = getline(&line, &line_size, passwd_fd); if (read_result < 0) { if (!feof(passwd_fd)) { Log(LOG_LEVEL_ERR, "Error while reading password database: %s", GetErrorStr()); free(line); goto close_both; } else { break; } } else if (read_result >= sizeof(line)) { Log(LOG_LEVEL_ERR, "Unusually long line found in password database while editing user '%s'. Not updating.", puser); } // Editing the password database is risky business, so do as little parsing as possible. // Just enough to get the hash in there. char *field_start = NULL; char *field_end = NULL; field_start = strchr(line, ':'); if (field_start) { field_end = strchr(field_start + 1, ':'); } if (!field_start || !field_end) { Log(LOG_LEVEL_ERR, "Unexpected format found in password database while editing user '%s'. Not updating.", puser); free(line); goto close_both; } // Worst case length: Existing password is empty plus one '\n' and one '\0'. char new_line[strlen(line) + strlen(password) + 2]; *field_start = '\0'; *field_end = '\0'; if (strcmp(line, puser) == 0) { sprintf(new_line, "%s:%s:%s\n", line, password, field_end + 1); } else { sprintf(new_line, "%s:%s:%s\n", line, field_start + 1, field_end + 1); } free(line); size_t new_line_size = strlen(new_line); size_t written_so_far = 0; while (written_so_far < new_line_size) { clearerr(edit_fd); size_t written = fwrite(new_line, 1, new_line_size, edit_fd); if (written == 0) { const char *err_str; if (ferror(edit_fd)) { err_str = GetErrorStr(); } else { err_str = "Unknown error"; } Log(LOG_LEVEL_ERR, "Error while writing to file '%s'. (fwrite: '%s')", edit_file, err_str); goto close_both; } written_so_far += written; } } fclose(edit_fd); fclose(passwd_fd); if (!CopyFilePermissionsDisk(passwd_file, edit_file)) { Log(LOG_LEVEL_ERR, "Could not copy permissions from '%s' to '%s'", passwd_file, edit_file); goto unlock_passwd; } if (rename(edit_file, passwd_file) < 0) { Log(LOG_LEVEL_ERR, "Could not replace '%s' with edited password database '%s'. (rename: '%s')", passwd_file, edit_file, GetErrorStr()); goto unlock_passwd; } result = true; goto unlock_passwd; close_both: fclose(edit_fd); unlink(edit_file); close_passwd_fd: fclose(passwd_fd); unlock_passwd: ulckpwdf(); return result; }
int LoadProcessTable(Item **procdata) { FILE *prp; char pscomm[CF_MAXLINKSIZE]; Item *rootprocs = NULL; Item *otherprocs = NULL; if (PROCESSTABLE) { Log(LOG_LEVEL_VERBOSE, "Reusing cached process table"); return true; } CheckPsLineLimitations(); const char *psopts = GetProcessOptions(); snprintf(pscomm, CF_MAXLINKSIZE, "%s %s", VPSCOMM[VPSHARDCLASS], psopts); Log(LOG_LEVEL_VERBOSE, "Observe process table with %s", pscomm); if ((prp = cf_popen(pscomm, "r", false)) == NULL) { Log(LOG_LEVEL_ERR, "Couldn't open the process list with command '%s'. (popen: %s)", pscomm, GetErrorStr()); return false; } size_t vbuff_size = CF_BUFSIZE; char *vbuff = xmalloc(vbuff_size); # ifdef HAVE_GETZONEID char *names[CF_PROCCOLS]; int start[CF_PROCCOLS]; int end[CF_PROCCOLS]; Seq *pidlist = SeqNew(1, NULL); Seq *rootpidlist = SeqNew(1, NULL); bool global_zone = IsGlobalZone(); if (global_zone) { int res = ZLoadProcesstable(pidlist, rootpidlist); if (res == false) { Log(LOG_LEVEL_ERR, "Unable to load solaris zone process table."); return false; } } # endif for (;;) { ssize_t res = CfReadLine(&vbuff, &vbuff_size, prp); if (res == -1) { if (!feof(prp)) { Log(LOG_LEVEL_ERR, "Unable to read process list with command '%s'. (fread: %s)", pscomm, GetErrorStr()); cf_pclose(prp); free(vbuff); return false; } else { break; } } Chop(vbuff, vbuff_size); # ifdef HAVE_GETZONEID if (global_zone) { if (strstr(vbuff, "PID") != NULL) { /* this is the banner so get the column header names for later use*/ GetProcessColumnNames(vbuff, &names[0], start, end); } else { int gpid = ExtractPid(vbuff, names, end); if (!IsGlobalProcess(gpid, pidlist, rootpidlist)) { continue; } } } # endif AppendItem(procdata, vbuff, ""); } cf_pclose(prp); /* Now save the data */ snprintf(vbuff, CF_MAXVARSIZE, "%s/state/cf_procs", CFWORKDIR); RawSaveItemList(*procdata, vbuff, NewLineMode_Unix); # ifdef HAVE_GETZONEID if (global_zone) /* pidlist and rootpidlist are empty if we're not in the global zone */ { Item *ip = *procdata; while (ip != NULL) { ZCopyProcessList(&rootprocs, ip, rootpidlist, names, end); ip = ip->next; } ReverseItemList(rootprocs); ip = *procdata; while (ip != NULL) { ZCopyProcessList(&otherprocs, ip, pidlist, names, end); ip = ip->next; } ReverseItemList(otherprocs); } else # endif { CopyList(&rootprocs, *procdata); CopyList(&otherprocs, *procdata); while (DeleteItemNotContaining(&rootprocs, "root")) { } while (DeleteItemContaining(&otherprocs, "root")) { } } if (otherprocs) { PrependItem(&rootprocs, otherprocs->name, NULL); } snprintf(vbuff, CF_MAXVARSIZE, "%s/state/cf_rootprocs", CFWORKDIR); RawSaveItemList(rootprocs, vbuff, NewLineMode_Unix); DeleteItemList(rootprocs); snprintf(vbuff, CF_MAXVARSIZE, "%s/state/cf_otherprocs", CFWORKDIR); RawSaveItemList(otherprocs, vbuff, NewLineMode_Unix); DeleteItemList(otherprocs); free(vbuff); return true; }
static void TransformGidsToGroups(StringSet **list) { StringSet *new_list = StringSetNew(); StringSetIterator i = StringSetIteratorInit(*list); const char *data; for (data = StringSetIteratorNext(&i); data; data = StringSetIteratorNext(&i)) { if (strlen(data) != strspn(data, "0123456789")) { // Cannot possibly be a gid. StringSetAdd(new_list, xstrdup(data)); continue; } // In groups vs gids, groups take precedence. So check if it exists. struct group *group_info = GetGrEntry(data, &EqualGroupName); if (!group_info) { if (errno == 0) { group_info = GetGrEntry(data, &EqualGid); if (!group_info) { if (errno != 0) { Log(LOG_LEVEL_ERR, "Error while checking group name '%s': %s", data, GetErrorStr()); StringSetDestroy(new_list); return; } // Neither group nor gid is found. This will lead to an error later, but we don't // handle that here. } else { // Replace gid with group name. StringSetAdd(new_list, xstrdup(group_info->gr_name)); } } else { Log(LOG_LEVEL_ERR, "Error while checking group name '%s': '%s'", data, GetErrorStr()); StringSetDestroy(new_list); return; } } else { StringSetAdd(new_list, xstrdup(data)); } } StringSet *old_list = *list; *list = new_list; StringSetDestroy(old_list); }
static bool MissingInputFile(const char *input_file) { struct stat sb; if (stat(input_file, &sb) == -1) { Log(LOG_LEVEL_ERR, "There is no readable input file at '%s'. (stat: %s)", input_file, GetErrorStr()); return true; } return false; }
/* Load processes using zone-aware ps * to obtain solaris list of global * process ids for root and non-root * users to lookup later */ int ZLoadProcesstable(Seq *pidlist, Seq *rootpidlist) { char *names[CF_PROCCOLS]; int start[CF_PROCCOLS]; int end[CF_PROCCOLS]; int index = 0; const char *pscmd = "/usr/bin/ps -Aleo zone,user,pid"; FILE *psf = cf_popen(pscmd, "r", false); if (psf == NULL) { Log(LOG_LEVEL_ERR, "ZLoadProcesstable: Couldn't open the process list with command %s.", pscmd); return false; } size_t pbuff_size = CF_BUFSIZE; char *pbuff = xmalloc(pbuff_size); while (true) { ssize_t res = CfReadLine(&pbuff, &pbuff_size, psf); if (res == -1) { if (!feof(psf)) { Log(LOG_LEVEL_ERR, "IsGlobalProcess(char **, int): Unable to read process list with command '%s'. (fread: %s)", pscmd, GetErrorStr()); cf_pclose(psf); free(pbuff); return false; } else { break; } } Chop(pbuff, pbuff_size); if (strstr(pbuff, "PID")) /* This line is the header. */ { GetProcessColumnNames(pbuff, &names[0], start, end); } else { int pid = ExtractPid(pbuff, &names[0], end); size_t zone_offset = strspn(pbuff, " "); size_t zone_end_offset = strcspn(pbuff + zone_offset, " ") + zone_offset; size_t user_offset = strspn(pbuff + zone_end_offset, " ") + zone_end_offset; size_t user_end_offset = strcspn(pbuff + user_offset, " ") + user_offset; bool is_global = (zone_end_offset - zone_offset == 6 && strncmp(pbuff + zone_offset, "global", 6) == 0); bool is_root = (user_end_offset - user_offset == 4 && strncmp(pbuff + user_offset, "root", 4) == 0); if (is_global && is_root) { SeqAppend(rootpidlist, (void*)(intptr_t)pid); } else if (is_global && !is_root) { SeqAppend(pidlist, (void*)(intptr_t)pid); } } } cf_pclose(psf); free(pbuff); return true; }
void GenericAgentInitialize(EvalContext *ctx, GenericAgentConfig *config) { int force = false; struct stat statbuf, sb; char vbuff[CF_BUFSIZE]; char ebuff[CF_EXPANDSIZE]; #ifdef __MINGW32__ InitializeWindows(); #endif DetermineCfenginePort(); EvalContextClassPutHard(ctx, "any", "source=agent"); GenericAgentAddEditionClasses(ctx); /* Define trusted directories */ { const char *workdir = GetWorkDir(); if (!workdir) { FatalError(ctx, "Error determining working directory"); } strcpy(CFWORKDIR, workdir); MapName(CFWORKDIR); } OpenLog(LOG_USER); SetSyslogFacility(LOG_USER); Log(LOG_LEVEL_VERBOSE, "Work directory is %s", CFWORKDIR); snprintf(vbuff, CF_BUFSIZE, "%s%cupdate.conf", GetInputDir(), FILE_SEPARATOR); MakeParentDirectory(vbuff, force); snprintf(vbuff, CF_BUFSIZE, "%s%cbin%ccf-agent -D from_cfexecd", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(vbuff, force); snprintf(vbuff, CF_BUFSIZE, "%s%coutputs%cspooled_reports", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(vbuff, force); snprintf(vbuff, CF_BUFSIZE, "%s%clastseen%cintermittencies", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(vbuff, force); snprintf(vbuff, CF_BUFSIZE, "%s%creports%cvarious", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(vbuff, force); snprintf(vbuff, CF_BUFSIZE, "%s", GetInputDir()); if (stat(vbuff, &sb) == -1) { FatalError(ctx, " No access to WORKSPACE/inputs dir"); } else { chmod(vbuff, sb.st_mode | 0700); } snprintf(vbuff, CF_BUFSIZE, "%s%coutputs", CFWORKDIR, FILE_SEPARATOR); if (stat(vbuff, &sb) == -1) { FatalError(ctx, " No access to WORKSPACE/outputs dir"); } else { chmod(vbuff, sb.st_mode | 0700); } snprintf(ebuff, sizeof(ebuff), "%s%cstate%ccf_procs", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(ebuff, force); if (stat(ebuff, &statbuf) == -1) { CreateEmptyFile(ebuff); } snprintf(ebuff, sizeof(ebuff), "%s%cstate%ccf_rootprocs", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); if (stat(ebuff, &statbuf) == -1) { CreateEmptyFile(ebuff); } snprintf(ebuff, sizeof(ebuff), "%s%cstate%ccf_otherprocs", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR); if (stat(ebuff, &statbuf) == -1) { CreateEmptyFile(ebuff); } snprintf(ebuff, sizeof(ebuff), "%s%cstate%cprevious_state%c", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(ebuff, force); snprintf(ebuff, sizeof(ebuff), "%s%cstate%cdiff%c", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(ebuff, force); snprintf(ebuff, sizeof(ebuff), "%s%cstate%cuntracked%c", CFWORKDIR, FILE_SEPARATOR, FILE_SEPARATOR, FILE_SEPARATOR); MakeParentDirectory(ebuff, force); OpenNetwork(); CryptoInitialize(); CheckWorkingDirectories(ctx); /* Initialize keys and networking. cf-key, doesn't need keys. In fact it must function properly even without them, so that it generates them! */ if (config->agent_type != AGENT_TYPE_KEYGEN) { LoadSecretKeys(); char *bootstrapped_policy_server = ReadPolicyServerFile(CFWORKDIR); PolicyHubUpdateKeys(bootstrapped_policy_server); free(bootstrapped_policy_server); cfnet_init(); } size_t cwd_size = PATH_MAX; while (true) { char cwd[cwd_size]; if (!getcwd(cwd, cwd_size)) { if (errno == ERANGE) { cwd_size *= 2; continue; } Log(LOG_LEVEL_WARNING, "Could not determine current directory. (getcwd: '%s')", GetErrorStr()); break; } EvalContextSetLaunchDirectory(ctx, cwd); break; } if (!MINUSF) { GenericAgentConfigSetInputFile(config, GetInputDir(), "promises.cf"); } VIFELAPSED = 1; VEXPIREAFTER = 1; setlinebuf(stdout); if (config->agent_specific.agent.bootstrap_policy_server) { snprintf(vbuff, CF_BUFSIZE, "%s%cfailsafe.cf", GetInputDir(), FILE_SEPARATOR); if (stat(vbuff, &statbuf) == -1) { GenericAgentConfigSetInputFile(config, GetInputDir(), "failsafe.cf"); } else { GenericAgentConfigSetInputFile(config, GetInputDir(), vbuff); } } }
/** * @brief Writes a file with a contained release ID based on git SHA, * or file checksum if git SHA is not available. * @param filename the release_id file * @param dirname the directory to checksum or get the Git hash * @return True if successful */ static bool WriteReleaseIdFile(const char *filename, const char *dirname) { char release_id[GENERIC_AGENT_CHECKSUM_SIZE]; bool have_release_id = GeneratePolicyReleaseID(release_id, sizeof(release_id), dirname); if (!have_release_id) { return false; } int fd = creat(filename, 0600); if (fd == -1) { Log(LOG_LEVEL_ERR, "While writing policy release ID file '%s', could not create file (creat: %s)", filename, GetErrorStr()); return false; } JsonElement *info = JsonObjectCreate(3); JsonObjectAppendString(info, "releaseId", release_id); Writer *w = FileWriter(fdopen(fd, "w")); JsonWrite(w, info, 0); WriterClose(w); JsonDestroy(info); Log(LOG_LEVEL_VERBOSE, "Saved policy release ID file '%s'", filename); return true; }
/** * @brief Writes a file with a contained timestamp to mark a policy file as validated * @param filename the filename * @return True if successful. */ static bool WritePolicyValidatedFile(ARG_UNUSED const GenericAgentConfig *config, const char *filename) { if (!MakeParentDirectory(filename, true)) { Log(LOG_LEVEL_ERR, "While writing policy validated marker file '%s', could not create directory (MakeParentDirectory: %s)", filename, GetErrorStr()); return false; } int fd = creat(filename, 0600); if (fd == -1) { Log(LOG_LEVEL_ERR, "While writing policy validated marker file '%s', could not create file (creat: %s)", filename, GetErrorStr()); return false; } JsonElement *info = JsonObjectCreate(3); JsonObjectAppendInteger(info, "timestamp", time(NULL)); Writer *w = FileWriter(fdopen(fd, "w")); JsonWrite(w, info, 0); WriterClose(w); JsonDestroy(info); Log(LOG_LEVEL_VERBOSE, "Saved policy validated marker file '%s'", filename); return true; }
static bool ConsiderFile(const char *nodename, const char *path, struct stat *stat) { int i; const char *sp; if (strlen(nodename) < 1) { Log(LOG_LEVEL_ERR, "Empty (null) filename detected in %s", path); return true; } if (IsItemIn(SUSPICIOUSLIST, nodename)) { if (stat && (S_ISREG(stat->st_mode) || S_ISLNK(stat->st_mode))) { Log(LOG_LEVEL_ERR, "Suspicious file %s found in %s", nodename, path); return false; } } if (strcmp(nodename, "...") == 0) { Log(LOG_LEVEL_VERBOSE, "Possible DFS/FS cell node detected in %s...", path); return true; } for (i = 0; SKIPFILES[i] != NULL; i++) { if (strcmp(nodename, SKIPFILES[i]) == 0) { Log(LOG_LEVEL_DEBUG, "Filename '%s/%s' is classified as ignorable", path, nodename); return false; } } if ((strcmp("[", nodename) == 0) && (strcmp("/usr/bin", path) == 0)) { #if defined(__linux__) return true; #endif } for (sp = nodename; *sp != '\0'; sp++) { if ((*sp > 31) && (*sp < 127)) { break; } } for (sp = nodename; *sp != '\0'; sp++) /* Check for files like ".. ." */ { if ((*sp != '.') && (!isspace((int)*sp))) { return true; } } if (stat == NULL) { Log(LOG_LEVEL_VERBOSE, "Couldn't stat '%s/%s'. (cf_lstat: %s)", path, nodename, GetErrorStr()); return true; } if ((stat->st_size == 0) && LogGetGlobalLevel() < LOG_LEVEL_INFO) /* No sense in warning about empty files */ { return false; } Log(LOG_LEVEL_ERR, "Suspicious looking file object '%s' masquerading as hidden file in '%s'", nodename, path); if (S_ISLNK(stat->st_mode)) { Log(LOG_LEVEL_INFO, " %s is a symbolic link", nodename); } else if (S_ISDIR(stat->st_mode)) { Log(LOG_LEVEL_INFO, " %s is a directory", nodename); } Log(LOG_LEVEL_VERBOSE, "[%s] has size %ld and full mode %o", nodename, (unsigned long) (stat->st_size), (unsigned int) (stat->st_mode)); return true; }
bool CopyFileExtendedAttributesDisk(const char *source, const char *destination) { #if defined(WITH_XATTR) // Extended attributes include both POSIX ACLs and SELinux contexts. ssize_t attr_raw_names_size; char attr_raw_names[CF_BUFSIZE]; attr_raw_names_size = llistxattr(source, attr_raw_names, sizeof(attr_raw_names)); if (attr_raw_names_size < 0) { if (errno == ENOTSUP || errno == ENODATA) { return true; } else { Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (llistxattr: %s)", source, destination, GetErrorStr()); return false; } } int pos; for (pos = 0; pos < attr_raw_names_size;) { const char *current = attr_raw_names + pos; pos += strlen(current) + 1; char data[CF_BUFSIZE]; int datasize = lgetxattr(source, current, data, sizeof(data)); if (datasize < 0) { if (errno == ENOTSUP) { continue; } else { Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lgetxattr: %s: %s)", source, destination, GetErrorStr(), current); return false; } } int ret = lsetxattr(destination, current, data, datasize, 0); if (ret < 0) { if (errno == ENOTSUP) { continue; } else { Log(LOG_LEVEL_ERR, "Can't copy extended attributes from '%s' to '%s'. (lsetxattr: %s: %s)", source, destination, GetErrorStr(), current); return false; } } } #else // !WITH_XATTR // ACLs are included in extended attributes, but fall back to CopyACLs if xattr is not available. if (!CopyACLs(source, destination)) { return false; } #endif return true; }
int ExpandLinks(char *dest, const char *from, int level) { char buff[CF_BUFSIZE]; char node[CF_MAXLINKSIZE]; struct stat statbuf; int lastnode = false; memset(dest, 0, CF_BUFSIZE); if (level >= CF_MAXLINKLEVEL) { Log(LOG_LEVEL_ERR, "Too many levels of symbolic links to evaluate absolute path"); return false; } const char *sp = from; while (*sp != '\0') { if (*sp == FILE_SEPARATOR) { sp++; continue; } sscanf(sp, "%[^/]", node); sp += strlen(node); if (*sp == '\0') { lastnode = true; } if (strcmp(node, ".") == 0) { continue; } if (strcmp(node, "..") == 0) { continue; } else { strcat(dest, "/"); } strcat(dest, node); if (lstat(dest, &statbuf) == -1) /* File doesn't exist so we can stop here */ { Log(LOG_LEVEL_ERR, "Can't stat '%s' in ExpandLinks. (lstat: %s)", dest, GetErrorStr()); return false; } if (S_ISLNK(statbuf.st_mode)) { memset(buff, 0, CF_BUFSIZE); if (readlink(dest, buff, CF_BUFSIZE - 1) == -1) { Log(LOG_LEVEL_ERR, "Expand links can't stat '%s'. (readlink: %s)", dest, GetErrorStr()); return false; } else { if (buff[0] == '.') { ChopLastNode(dest); AddSlash(dest); /* TODO pass and use parameter dest_size. */ size_t ret = strlcat(dest, buff, CF_BUFSIZE); if (ret >= CF_BUFSIZE) { Log(LOG_LEVEL_ERR, "Internal limit reached in ExpandLinks()," " path too long: '%s' + '%s'", dest, buff); return false; } } else if (IsAbsoluteFileName(buff)) { strcpy(dest, buff); DeleteSlash(dest); if (strcmp(dest, from) == 0) { Log(LOG_LEVEL_DEBUG, "No links to be expanded"); return true; } if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1))) { return false; } } else { ChopLastNode(dest); AddSlash(dest); /* TODO use param dest_size. */ size_t ret = strlcat(dest, buff, CF_BUFSIZE); if (ret >= CF_BUFSIZE) { Log(LOG_LEVEL_ERR, "Internal limit reached in ExpandLinks end," " path too long: '%s' + '%s'", dest, buff); return false; } DeleteSlash(dest); if (strcmp(dest, from) == 0) { Log(LOG_LEVEL_DEBUG, "No links to be expanded"); return true; } memset(buff, 0, CF_BUFSIZE); if ((!lastnode) && (!ExpandLinks(buff, dest, level + 1))) { return false; } } } } } return true; }
static ActionResult RepairExec(EvalContext *ctx, Attributes a, Promise *pp) { char line[CF_BUFSIZE], eventname[CF_BUFSIZE]; char cmdline[CF_BUFSIZE]; char comm[20]; int outsourced, count = 0; #if !defined(__MINGW32__) mode_t maskval = 0; #endif FILE *pfp; char cmdOutBuf[CF_BUFSIZE]; int cmdOutBufPos = 0; int lineOutLen; if (a.contain.shelltype == SHELL_TYPE_NONE) { if (!IsExecutable(CommandArg0(pp->promiser))) { Log(LOG_LEVEL_ERR, "'%s' promises to be executable but isn't", pp->promiser); if (strchr(pp->promiser, ' ')) { Log(LOG_LEVEL_VERBOSE, "Paths with spaces must be inside escaped quoutes (e.g. \\\"%s\\\")", pp->promiser); } return ACTION_RESULT_FAILED; } else { Log(LOG_LEVEL_VERBOSE, "Promiser string contains a valid executable '%s' - ok", CommandArg0(pp->promiser)); } } char timeout_str[CF_BUFSIZE]; if (a.contain.timeout == CF_NOINT) { snprintf(timeout_str, CF_BUFSIZE, "no timeout"); } else { snprintf(timeout_str, CF_BUFSIZE, "timeout=%ds", a.contain.timeout); } char owner_str[CF_BUFSIZE] = ""; if (a.contain.owner != -1) { snprintf(owner_str, CF_BUFSIZE, ",uid=%ju", (uintmax_t)a.contain.owner); } char group_str[CF_BUFSIZE] = ""; if (a.contain.group != -1) { snprintf(group_str, CF_BUFSIZE, ",gid=%ju", (uintmax_t)a.contain.group); } snprintf(cmdline, CF_BUFSIZE, "%s%s%s", pp->promiser, a.args ? " " : "", a.args ? a.args : ""); Log(LOG_LEVEL_INFO, "Executing '%s%s%s' ... '%s'", timeout_str, owner_str, group_str, cmdline); BeginMeasure(); if (DONTDO && (!a.contain.preview)) { Log(LOG_LEVEL_ERR, "Would execute script '%s'", cmdline); return ACTION_RESULT_OK; } if (a.transaction.action != cfa_fix) { Log(LOG_LEVEL_ERR, "Command '%s' needs to be executed, but only warning was promised", cmdline); return ACTION_RESULT_OK; } CommandPrefix(cmdline, comm); if (a.transaction.background) { #ifdef __MINGW32__ outsourced = true; #else Log(LOG_LEVEL_VERBOSE, "Backgrounding job '%s'", cmdline); outsourced = fork(); #endif } else { outsourced = false; } if (outsourced || (!a.transaction.background)) // work done here: either by child or non-background parent { if (a.contain.timeout != CF_NOINT) { SetTimeOut(a.contain.timeout); } #ifndef __MINGW32__ Log(LOG_LEVEL_VERBOSE, "(Setting umask to %jo)", (uintmax_t)a.contain.umask); maskval = umask(a.contain.umask); if (a.contain.umask == 0) { Log(LOG_LEVEL_VERBOSE, "Programming '%s' running with umask 0! Use umask= to set", cmdline); } #endif /* !__MINGW32__ */ if (a.contain.shelltype == SHELL_TYPE_POWERSHELL) { #ifdef __MINGW32__ pfp = cf_popen_powershell_setuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); #else // !__MINGW32__ Log(LOG_LEVEL_ERR, "Powershell is only supported on Windows"); return ACTION_RESULT_FAILED; #endif // !__MINGW32__ } else if (a.contain.shelltype == SHELL_TYPE_USE) { pfp = cf_popen_shsetuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } else { pfp = cf_popensetuid(cmdline, "r", a.contain.owner, a.contain.group, a.contain.chdir, a.contain.chroot, a.transaction.background); } if (pfp == NULL) { Log(LOG_LEVEL_ERR, "Couldn't open pipe to command '%s'. (cf_popen: %s)", cmdline, GetErrorStr()); return ACTION_RESULT_FAILED; } for (;;) { ssize_t res = CfReadLine(line, CF_BUFSIZE, pfp); if (res == 0) { break; } if (res == -1) { Log(LOG_LEVEL_ERR, "Unable to read output from command '%s'. (fread: %s)", cmdline, GetErrorStr()); cf_pclose(pfp); return ACTION_RESULT_FAILED; } if (strstr(line, "cfengine-die")) { break; } if (a.contain.preview) { PreviewProtocolLine(line, cmdline); } if (a.module) { ModuleProtocol(ctx, cmdline, line, !a.contain.nooutput, PromiseGetNamespace(pp)); } else if ((!a.contain.nooutput) && (!EmptyString(line))) { lineOutLen = strlen(comm) + strlen(line) + 12; // if buffer is to small for this line, output it directly if (lineOutLen > sizeof(cmdOutBuf)) { Log(LOG_LEVEL_NOTICE, "Q: '%s': %s", comm, line); } else { if (cmdOutBufPos + lineOutLen > sizeof(cmdOutBuf)) { Log(LOG_LEVEL_NOTICE, "%s", cmdOutBuf); cmdOutBufPos = 0; } sprintf(cmdOutBuf + cmdOutBufPos, "Q: \"...%s\": %s\n", comm, line); cmdOutBufPos += (lineOutLen - 1); } count++; } } #ifdef __MINGW32__ if (outsourced) // only get return value if we waited for command execution { cf_pclose(pfp); } else #endif /* __MINGW32__ */ { int ret = cf_pclose(pfp); if (ret == -1) { cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_FAIL, pp, a, "Finished script '%s' - failed (abnormal termination)", pp->promiser); } else { VerifyCommandRetcode(ctx, ret, true, a, pp); } } } if (count) { if (cmdOutBufPos) { Log(LOG_LEVEL_NOTICE, "%s", cmdOutBuf); } Log(LOG_LEVEL_INFO, "Last %d quoted lines were generated by promiser '%s'", count, cmdline); } if (a.contain.timeout != CF_NOINT) { alarm(0); signal(SIGALRM, SIG_DFL); } Log(LOG_LEVEL_INFO, "Completed execution of '%s'", cmdline); #ifndef __MINGW32__ umask(maskval); #endif snprintf(eventname, CF_BUFSIZE - 1, "Exec(%s)", cmdline); #ifndef __MINGW32__ if ((a.transaction.background) && outsourced) { Log(LOG_LEVEL_VERBOSE, "Backgrounded command '%s' is done - exiting", cmdline); exit(0); } #endif /* !__MINGW32__ */ return ACTION_RESULT_OK; }
void VerifyOneUsersPromise (const char *puser, User u, PromiseResult *result, enum cfopaction action, EvalContext *ctx, const Attributes *a, const Promise *pp) { bool res; struct passwd *passwd_info; errno = 0; passwd_info = getpwnam(puser); // Apparently POSIX is ambiguous here. All the values below mean "not found". if (!passwd_info && errno != 0 && errno != ENOENT && errno != EBADF && errno != ESRCH && errno != EWOULDBLOCK && errno != EPERM) { Log(LOG_LEVEL_ERR, "Could not get information from user database. (getpwnam: '%s')", GetErrorStr()); return; } if (u.policy == USER_STATE_PRESENT || u.policy == USER_STATE_LOCKED) { if (passwd_info) { uint32_t cmap = 0; if (VerifyIfUserNeedsModifs (puser, u, passwd_info, &cmap)) { res = DoModifyUser (puser, u, passwd_info, cmap, action); if (res) { *result = PROMISE_RESULT_CHANGE; } else { *result = PROMISE_RESULT_FAIL; } } else { *result = PROMISE_RESULT_NOOP; } } else { res = DoCreateUser (puser, u, action, ctx, a, pp); if (res) { *result = PROMISE_RESULT_CHANGE; } else { *result = PROMISE_RESULT_FAIL; } } } else if (u.policy == USER_STATE_ABSENT) { if (passwd_info) { res = DoRemoveUser (puser, action); if (res) { *result = PROMISE_RESULT_CHANGE; } else { *result = PROMISE_RESULT_FAIL; } } else { *result = PROMISE_RESULT_NOOP; } } }
void StartServer(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) { int sd = -1, sd_reply; fd_set rset; struct timeval timeout; int ret_val; CfLock thislock; time_t starttime = time(NULL), last_collect = 0; struct sockaddr_storage cin; socklen_t addrlen = sizeof(cin); signal(SIGINT, HandleSignalsForDaemon); signal(SIGTERM, HandleSignalsForDaemon); signal(SIGHUP, SIG_IGN); signal(SIGPIPE, SIG_IGN); signal(SIGUSR1, HandleSignalsForDaemon); signal(SIGUSR2, HandleSignalsForDaemon); sd = SetServerListenState(ctx, QUEUESIZE); TransactionContext tc = { .ifelapsed = 0, .expireafter = 1, }; Policy *server_cfengine_policy = PolicyNew(); Promise *pp = NULL; { Bundle *bp = PolicyAppendBundle(server_cfengine_policy, NamespaceDefault(), "server_cfengine_bundle", "agent", NULL, NULL); PromiseType *tp = BundleAppendPromiseType(bp, "server_cfengine"); pp = PromiseTypeAppendPromise(tp, config->input_file, (Rval) { NULL, RVAL_TYPE_NOPROMISEE }, NULL); } assert(pp); thislock = AcquireLock(ctx, pp->promiser, VUQNAME, CFSTARTTIME, tc, pp, false); if (thislock.lock == NULL) { PolicyDestroy(server_cfengine_policy); return; } Log(LOG_LEVEL_INFO, "cf-serverd starting %.24s", ctime(&starttime)); if (sd != -1) { Log(LOG_LEVEL_VERBOSE, "Listening for connections ..."); } #ifdef __MINGW32__ if (!NO_FORK) { Log(LOG_LEVEL_VERBOSE, "Windows does not support starting processes in the background - starting in foreground"); } #else /* !__MINGW32__ */ if ((!NO_FORK) && (fork() != 0)) { _exit(0); } if (!NO_FORK) { ActAsDaemon(sd); } #endif /* !__MINGW32__ */ WritePID("cf-serverd.pid"); /* Andrew Stribblehill <*****@*****.**> -- close sd on exec */ #ifndef __MINGW32__ fcntl(sd, F_SETFD, FD_CLOEXEC); #endif while (!IsPendingTermination()) { time_t now = time(NULL); /* Note that this loop logic is single threaded, but ACTIVE_THREADS might still change in threads pertaining to service handling */ if (ThreadLock(cft_server_children)) { if (ACTIVE_THREADS == 0) { CheckFileChanges(ctx, policy, config); } ThreadUnlock(cft_server_children); } // Check whether we should try to establish peering with a hub if ((COLLECT_INTERVAL > 0) && ((now - last_collect) > COLLECT_INTERVAL)) { TryCollectCall(); last_collect = now; continue; } /* check if listening is working */ if (sd != -1) { // Look for normal incoming service requests FD_ZERO(&rset); FD_SET(sd, &rset); /* Set 1 second timeout for select, so that signals are handled in * a timely manner */ timeout.tv_sec = 1; timeout.tv_usec = 0; Log(LOG_LEVEL_DEBUG, "Waiting at incoming select..."); ret_val = select((sd + 1), &rset, NULL, NULL, &timeout); if (ret_val == -1) /* Error received from call to select */ { if (errno == EINTR) { continue; } else { Log(LOG_LEVEL_ERR, "select failed. (select: %s)", GetErrorStr()); exit(1); } } else if (!ret_val) /* No data waiting, we must have timed out! */ { continue; } Log(LOG_LEVEL_VERBOSE, "Accepting a connection"); if ((sd_reply = accept(sd, (struct sockaddr *) &cin, &addrlen)) != -1) { /* Just convert IP address to string, no DNS lookup. */ char ipaddr[CF_MAX_IP_LEN] = ""; getnameinfo((struct sockaddr *) &cin, addrlen, ipaddr, sizeof(ipaddr), NULL, 0, NI_NUMERICHOST); ServerEntryPoint(ctx, sd_reply, ipaddr); } } } PolicyDestroy(server_cfengine_policy); } /*********************************************************************/ /* Level 2 */ /*********************************************************************/ int InitServer(size_t queue_size) { int sd = -1; if ((sd = OpenReceiverChannel()) == -1) { Log(LOG_LEVEL_ERR, "Unable to start server"); exit(1); } if (listen(sd, queue_size) == -1) { Log(LOG_LEVEL_ERR, "listen failed. (listen: %s)", GetErrorStr()); exit(1); } return sd; }
static bool GroupGetUserMembership (const char *user, StringSet *result) { bool ret = true; struct group *group_info; setgrent(); while (true) { errno = 0; group_info = getgrent(); if (!group_info) { if (errno) { Log(LOG_LEVEL_ERR, "Error while getting group list. (getgrent: '%s')", GetErrorStr()); ret = false; } break; } for (int i = 0; group_info->gr_mem[i] != NULL; i++) { if (strcmp(user, group_info->gr_mem[i]) == 0) { StringSetAdd(result, xstrdup(group_info->gr_name)); break; } } } endgrent(); return ret; }
int OpenReceiverChannel(void) { struct addrinfo *response, *ap; struct addrinfo query = { .ai_flags = AI_PASSIVE, .ai_family = AF_UNSPEC, .ai_socktype = SOCK_STREAM }; /* Listen to INADDR(6)_ANY if BINDINTERFACE unset. */ char *ptr = NULL; if (BINDINTERFACE[0] != '\0') { ptr = BINDINTERFACE; } /* Resolve listening interface. */ if (getaddrinfo(ptr, STR_CFENGINEPORT, &query, &response) != 0) { Log(LOG_LEVEL_ERR, "DNS/service lookup failure. (getaddrinfo: %s)", GetErrorStr()); return -1; } int sd = -1; for (ap = response; ap != NULL; ap = ap->ai_next) { if ((sd = socket(ap->ai_family, ap->ai_socktype, ap->ai_protocol)) == -1) { continue; } int yes = 1; if (setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) { Log(LOG_LEVEL_ERR, "Socket option SO_REUSEADDR was not accepted. (setsockopt: %s)", GetErrorStr()); exit(1); } struct linger cflinger = { .l_onoff = 1, .l_linger = 60 }; if (setsockopt(sd, SOL_SOCKET, SO_LINGER, &cflinger, sizeof(cflinger)) == -1) { Log(LOG_LEVEL_ERR, "Socket option SO_LINGER was not accepted. (setsockopt: %s)", GetErrorStr()); exit(1); } if (bind(sd, ap->ai_addr, ap->ai_addrlen) != -1) { if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) { /* Convert IP address to string, no DNS lookup performed. */ char txtaddr[CF_MAX_IP_LEN] = ""; getnameinfo(ap->ai_addr, ap->ai_addrlen, txtaddr, sizeof(txtaddr), NULL, 0, NI_NUMERICHOST); Log(LOG_LEVEL_DEBUG, "Bound to address '%s' on '%s' = %d", txtaddr, CLASSTEXT[VSYSTEMHARDCLASS], VSYSTEMHARDCLASS); } break; } else { Log(LOG_LEVEL_ERR, "Could not bind server address. (bind: %s)", GetErrorStr()); cf_closesocket(sd); } } if (sd < 0) { Log(LOG_LEVEL_ERR, "Couldn't open/bind a socket"); exit(1); } freeaddrinfo(response); return sd; } /*********************************************************************/ /* Level 3 */ /*********************************************************************/ void CheckFileChanges(EvalContext *ctx, Policy **policy, GenericAgentConfig *config) { Log(LOG_LEVEL_DEBUG, "Checking file updates for input file '%s'", config->input_file); if (NewPromiseProposals(ctx, config, InputFiles(ctx, *policy))) { Log(LOG_LEVEL_VERBOSE, "New promises detected..."); if (CheckPromises(config)) { Log(LOG_LEVEL_INFO, "Rereading policy file '%s'", config->input_file); /* Free & reload -- lock this to avoid access errors during reload */ EvalContextHeapClear(ctx); DeleteItemList(IPADDRESSES); IPADDRESSES = NULL; DeleteItemList(SV.trustkeylist); DeleteItemList(SV.skipverify); DeleteItemList(SV.attackerlist); DeleteItemList(SV.nonattackerlist); DeleteItemList(SV.multiconnlist); DeleteAuthList(SV.admit); DeleteAuthList(SV.deny); DeleteAuthList(SV.varadmit); DeleteAuthList(SV.vardeny); DeleteAuthList(SV.roles); //DeleteRlist(VINPUTLIST); This is just a pointer, cannot free it ScopeDeleteAll(); strcpy(VDOMAIN, "undefined.domain"); POLICY_SERVER[0] = '\0'; SV.admit = NULL; SV.admittop = NULL; SV.varadmit = NULL; SV.varadmittop = NULL; SV.deny = NULL; SV.denytop = NULL; SV.vardeny = NULL; SV.vardenytop = NULL; SV.roles = NULL; SV.rolestop = NULL; SV.trustkeylist = NULL; SV.skipverify = NULL; SV.attackerlist = NULL; SV.nonattackerlist = NULL; SV.multiconnlist = NULL; PolicyDestroy(*policy); *policy = NULL; { char *existing_policy_server = ReadPolicyServerFile(GetWorkDir()); SetPolicyServer(ctx, existing_policy_server); free(existing_policy_server); } GetNameInfo3(ctx, AGENT_TYPE_SERVER); GetInterfacesInfo(ctx, AGENT_TYPE_SERVER); Get3Environment(ctx, AGENT_TYPE_SERVER); BuiltinClasses(ctx); OSClasses(ctx); KeepHardClasses(ctx); EvalContextHeapAddHard(ctx, CF_AGENTTYPES[config->agent_type]); SetReferenceTime(ctx, true); *policy = GenericAgentLoadPolicy(ctx, config); KeepPromises(ctx, *policy, config); Summarize(); } else { Log(LOG_LEVEL_INFO, "File changes contain errors -- ignoring"); PROMISETIME = time(NULL); } } else { Log(LOG_LEVEL_DEBUG, "No new promises found"); } }
static bool VerifyIfUserNeedsModifs (const char *puser, User u, const struct passwd *passwd_info, uint32_t *changemap) { if (u.description != NULL && strcmp (u.description, passwd_info->pw_gecos)) { CFUSR_SETBIT (*changemap, i_comment); } if (u.uid != NULL && (atoi (u.uid) != passwd_info->pw_uid)) { CFUSR_SETBIT (*changemap, i_uid); } if (u.home_dir != NULL && strcmp (u.home_dir, passwd_info->pw_dir)) { CFUSR_SETBIT (*changemap, i_home); } if (u.shell != NULL && strcmp (u.shell, passwd_info->pw_shell)) { CFUSR_SETBIT (*changemap, i_shell); } bool account_is_locked = IsAccountLocked(puser, passwd_info); if ((!account_is_locked && u.policy == USER_STATE_LOCKED) || (account_is_locked && u.policy != USER_STATE_LOCKED)) { CFUSR_SETBIT(*changemap, i_locked); } // Don't bother with passwords if the account is going to be locked anyway. if (u.password != NULL && strcmp (u.password, "") && u.policy != USER_STATE_LOCKED) { if (!IsPasswordCorrect(puser, u.password, u.password_format, passwd_info)) { CFUSR_SETBIT (*changemap, i_password); } } if (SafeStringLength(u.group_primary)) { bool group_could_be_gid = (strlen(u.group_primary) == strspn(u.group_primary, "0123456789")); int gid; // We try name first, even if it looks like a gid. Only fall back to gid. struct group *group_info; errno = 0; group_info = getgrnam(u.group_primary); // Apparently POSIX is ambiguous here. All the values below mean "not found". if (!group_info && errno != 0 && errno != ENOENT && errno != EBADF && errno != ESRCH && errno != EWOULDBLOCK && errno != EPERM) { Log(LOG_LEVEL_ERR, "Could not obtain information about group '%s'. (getgrnam: '%s')", u.group_primary, GetErrorStr()); gid = -1; } else if (!group_info) { if (group_could_be_gid) { gid = atoi(u.group_primary); } else { Log(LOG_LEVEL_ERR, "No such group '%s'.", u.group_primary); gid = -1; } } else { gid = group_info->gr_gid; } if (gid != passwd_info->pw_gid) { CFUSR_SETBIT (*changemap, i_group); } } if (u.groups_secondary != NULL) { StringSet *wanted_groups = StringSetNew(); for (Rlist *ptr = u.groups_secondary; ptr; ptr = ptr->next) { if (strcmp(RvalScalarValue(ptr->val), CF_NULL_VALUE) != 0) { StringSetAdd(wanted_groups, xstrdup(RvalScalarValue(ptr->val))); } } TransformGidsToGroups(&wanted_groups); StringSet *current_groups = StringSetNew(); if (!GroupGetUserMembership (puser, current_groups)) { CFUSR_SETBIT (*changemap, i_groups); } else if (!StringSetIsEqual (current_groups, wanted_groups)) { CFUSR_SETBIT (*changemap, i_groups); } StringSetDestroy(current_groups); StringSetDestroy(wanted_groups); } //////////////////////////////////////////// if (*changemap == 0) { return false; } else { return true; } }
int IsNewerFileTree(char *dir, time_t reftime) { const struct dirent *dirp; char path[CF_BUFSIZE] = { 0 }; Dir *dirh; struct stat sb; // Assumes that race conditions on the file path are unlikely and unimportant if (lstat(dir, &sb) == -1) { Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (stat: %s)", dir, GetErrorStr()); // return true to provoke update return true; } if (S_ISDIR(sb.st_mode)) { if (sb.st_mtime > reftime) { Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", dir); return true; } } if ((dirh = DirOpen(dir)) == NULL) { Log(LOG_LEVEL_ERR, "Unable to open directory '%s' in IsNewerFileTree. (opendir: %s)", dir, GetErrorStr()); return false; } else { for (dirp = DirRead(dirh); dirp != NULL; dirp = DirRead(dirh)) { if (!strcmp(dirp->d_name, ".") || !strcmp(dirp->d_name, "..")) { continue; } strncpy(path, dir, CF_BUFSIZE - 1); if (!JoinPath(path, dirp->d_name)) { Log(LOG_LEVEL_ERR, "Internal limit: Buffer ran out of space adding %s to %s in IsNewerFileTree", dir, path); DirClose(dirh); return false; } if (lstat(path, &sb) == -1) { Log(LOG_LEVEL_ERR, "Unable to stat directory '%s' in IsNewerFileTree. (lstat: %s)", path, GetErrorStr()); DirClose(dirh); // return true to provoke update return true; } if (S_ISDIR(sb.st_mode)) { if (sb.st_mtime > reftime) { Log(LOG_LEVEL_VERBOSE, " >> Detected change in %s", path); DirClose(dirh); return true; } else { if (IsNewerFileTree(path, reftime)) { DirClose(dirh); return true; } } } } } DirClose(dirh); return false; }
static int CheckDefaultEqualsAccessACL(EvalContext *ctx, const char *file_path, Attributes a, const Promise *pp, PromiseResult *result) { acl_t acl_access; acl_t acl_default; int equals; int retval = false; acl_access = NULL; acl_default = NULL; if ((acl_access = acl_get_file(file_path, ACL_TYPE_ACCESS)) == NULL) { Log(LOG_LEVEL_ERR, "Could not find an ACL for '%s'. (acl_get_file: %s)", file_path, GetErrorStr()); return false; } acl_default = acl_get_file(file_path, ACL_TYPE_DEFAULT); if (acl_default == NULL) { Log(LOG_LEVEL_ERR, "Could not find default ACL for '%s'. (acl_get_file: %s)", file_path, GetErrorStr()); acl_free(acl_access); return false; } equals = ACLEquals(acl_access, acl_default); switch (equals) { case 0: // they equal, as desired cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Default ACL on '%s' needs no modification.", file_path); retval = true; break; case 1: // set access ACL as default ACL switch (a.transaction.action) { case cfa_warn: cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, a, "Default ACL on '%s' needs to be copied from access ACL.", file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_WARN); break; case cfa_fix: if (!DONTDO) { if ((acl_set_file(file_path, ACL_TYPE_DEFAULT, acl_access)) != 0) { Log(LOG_LEVEL_ERR, "Could not set default ACL to access"); acl_free(acl_access); acl_free(acl_default); return false; } } cfPS(ctx, LOG_LEVEL_INFO, PROMISE_RESULT_CHANGE, pp, a, "Default ACL on '%s' successfully copied from access ACL.", file_path); *result = PromiseResultUpdate(*result, PROMISE_RESULT_CHANGE); retval = true; break; default: ProgrammingError("CFEngine: internal error: illegal file action"); retval = false; } break; default: retval = false; Log(LOG_LEVEL_ERR, "Unable to compare access and default ACEs"); } acl_free(acl_access); acl_free(acl_default); return retval; }
ENTERPRISE_VOID_FUNC_2ARG_DEFINE_STUB(void, LogTotalCompliance, const char *, version, int, background_tasks) { double total = (double) (PR_KEPT + PR_NOTKEPT + PR_REPAIRED) / 100.0; char string[CF_BUFSIZE] = { 0 }; snprintf(string, CF_BUFSIZE, "Outcome of version %s (" CF_AGENTC "-%d): Promises observed to be kept %.2f%%, Promises repaired %.2f%%, Promises not repaired %.2f%%", version, background_tasks, (double) PR_KEPT / total, (double) PR_REPAIRED / total, (double) PR_NOTKEPT / total); Log(LOG_LEVEL_VERBOSE, "Logging total compliance, total '%s'", string); char filename[CF_BUFSIZE]; snprintf(filename, CF_BUFSIZE, "%s/%s", GetLogDir(), CF_PROMISE_LOG); MapName(filename); FILE *fout = fopen(filename, "a"); if (fout == NULL) { Log(LOG_LEVEL_ERR, "In total compliance logging, could not open file '%s'. (fopen: %s)", filename, GetErrorStr()); } else { fprintf(fout, "%jd,%jd: %s\n", (intmax_t)CFSTARTTIME, (intmax_t)time(NULL), string); fclose(fout); } }
static acl_entry_t FindACE(acl_t acl, acl_entry_t ace_find) { acl_entry_t ace_curr; acl_tag_t tag_curr; acl_tag_t tag_find; id_t *id_curr; id_t *id_find; int more_aces; int retv_tag; id_find = NULL; more_aces = acl_get_entry(acl, ACL_FIRST_ENTRY, &ace_curr); if (more_aces == -1) { Log(LOG_LEVEL_ERR, "Error reading acl. (acl_get_entry: %s)", GetErrorStr()); return NULL; } else if (more_aces == 0) { return NULL; } /* find the tag type and id we are looking for */ if (acl_get_tag_type(ace_find, &tag_find) != 0) { Log(LOG_LEVEL_ERR, "Error reading tag type. (acl_get_tag_type: %s)", GetErrorStr()); return NULL; } if (tag_find == ACL_USER || tag_find == ACL_GROUP) { id_find = acl_get_qualifier(ace_find); if (id_find == NULL) { Log(LOG_LEVEL_ERR, "Error reading tag type. (acl_get_qualifier: %s)", GetErrorStr()); return NULL; } } /* check if any of the aces match */ while (more_aces) { if ((retv_tag = acl_get_tag_type(ace_curr, &tag_curr)) != 0) { Log(LOG_LEVEL_ERR, "Unable to get tag type. (acl_get_tag_type: %s)", GetErrorStr()); acl_free(id_find); return NULL; } if (tag_curr == tag_find) { if (id_find == NULL) { return ace_curr; } id_curr = acl_get_qualifier(ace_curr); if (id_curr == NULL) { Log(LOG_LEVEL_ERR, "Couldn't extract qualifier. (acl_get_qualifier: %s)", GetErrorStr()); return NULL; } if (*id_curr == *id_find) { acl_free(id_find); acl_free(id_curr); return ace_curr; } acl_free(id_curr); } more_aces = acl_get_entry(acl, ACL_NEXT_ENTRY, &ace_curr); } if (id_find != NULL) { acl_free(id_find); } return NULL; }
void RemoteSysLog(int log_priority, const char *log_string) { time_t now = time(NULL); struct addrinfo query = { 0 }, *response = NULL; char strport[PRINTSIZE(unsigned)]; xsnprintf(strport, sizeof(strport), "%u", (unsigned) SYSLOG_PORT); query.ai_family = AF_UNSPEC; query.ai_socktype = SOCK_DGRAM; int err = getaddrinfo(SYSLOG_HOST, strport, &query, &response); if (err != 0) { Log(LOG_LEVEL_INFO, "Unable to find syslog_host or service: (%s/%s) %s", SYSLOG_HOST, strport, gai_strerror(err)); if (response != NULL) { freeaddrinfo(response); } return; } for (const struct addrinfo *ap = response; ap != NULL; ap = ap->ai_next) { /* No DNS lookup, just convert IP address to string. */ char txtaddr[CF_MAX_IP_LEN] = ""; getnameinfo(ap->ai_addr, ap->ai_addrlen, txtaddr, sizeof(txtaddr), NULL, 0, NI_NUMERICHOST); Log(LOG_LEVEL_VERBOSE, "Connect to syslog '%s' = '%s' on port '%s'", SYSLOG_HOST, txtaddr, strport); int sd = socket(ap->ai_family, ap->ai_socktype, IPPROTO_UDP); if (sd == -1) { Log(LOG_LEVEL_INFO, "Couldn't open a socket. (socket: %s)", GetErrorStr()); continue; } else { const size_t rfc3164_len = 1024; char message[rfc3164_len]; char timebuffer[26]; pid_t pid = getpid(); snprintf(message, sizeof(message), "<%i>%.15s %s %s[%d]: %s", log_priority | SYSLOG_FACILITY, cf_strtimestamp_local(now, timebuffer) + 4, VFQNAME, VPREFIX, pid, log_string); err = sendto(sd, message, strlen(message), 0, ap->ai_addr, ap->ai_addrlen); if (err == -1) { Log(LOG_LEVEL_VERBOSE, "Couldn't send '%s' to syslog server '%s'. (sendto: %s)", message, SYSLOG_HOST, GetErrorStr()); } else { Log(LOG_LEVEL_VERBOSE, "Syslog message: '%s' to server '%s'", message, SYSLOG_HOST); } close(sd); } } freeaddrinfo(response); }
static int ParseEntityPosixLinux(char **str, acl_entry_t ace, int *is_mask) { struct passwd *pwd; struct group *grp; acl_tag_t etype; size_t idsz; id_t id; char *ids; char *id_end; int result = true; int i; ids = NULL; // TODO: Support numeric id in addition to (user/group) name ? // Posix language: tag type, qualifier, permissions if (strncmp(*str, "user:"******"*", 2) == 0) { etype = ACL_USER_OBJ; id = 0; } else { etype = ACL_USER; pwd = getpwnam(ids); if (pwd == NULL) { Log(LOG_LEVEL_ERR, "Couldn't find user id for '%s'. (getpwnnam: %s)", ids, GetErrorStr()); free(ids); return false; } id = pwd->pw_uid; } } else if (strncmp(*str, "group:", 6) == 0) { *str += 6; // create null-terminated string for entity id id_end = index(*str, ':'); if (id_end == NULL) // entity id already null-terminated { idsz = strlen(*str); } else // copy entity-id to new null-terminated string { idsz = id_end - *str; } ids = xmalloc(idsz + 1); for (i = 0; i < idsz; i++) ids[i] = (*str)[i]; ids[idsz] = '\0'; *str += idsz; // file group if (strncmp(ids, "*", 2) == 0) { etype = ACL_GROUP_OBJ; id = 0; // TODO: Correct file group id ?? } else { etype = ACL_GROUP; grp = getgrnam(ids); if (grp == NULL) { Log(LOG_LEVEL_ERR, "Error looking up group id for %s", ids); free(ids); return false; } id = grp->gr_gid; } } else if (strncmp(*str, "all:", 4) == 0) { *str += 3; etype = ACL_OTHER; } else if (strncmp(*str, "mask:", 5) == 0) { *str += 4; etype = ACL_MASK; *is_mask = true; } else { Log(LOG_LEVEL_ERR, "ace does not start with user:/group:/all:/mask:"); return false; } if (acl_set_tag_type(ace, etype) != 0) { Log(LOG_LEVEL_ERR, "Could not set ACE tag type. (acl_set_tag_type: %s)", GetErrorStr()); result = false; } else if (etype == ACL_USER || etype == ACL_GROUP) { if ((acl_set_qualifier(ace, &id)) != 0) { Log(LOG_LEVEL_ERR, "Could not set ACE qualifier. (acl_set_qualifier: %s)", GetErrorStr()); result = false; } } if (ids != NULL) { free(ids); } return result; }
static PromiseResult VerifyFilePromise(EvalContext *ctx, char *path, const Promise *pp) { struct stat osb, oslb, dsb; CfLock thislock; int exists; Attributes a = GetFilesAttributes(ctx, pp); if (!FileSanityChecks(path, a, pp)) { ClearFilesAttributes(&a); return PROMISE_RESULT_NOOP; } EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_THIS, "promiser", path, CF_DATA_TYPE_STRING, "source=promise"); thislock = AcquireLock(ctx, path, VUQNAME, CFSTARTTIME, a.transaction, pp, false); if (thislock.lock == NULL) { ClearFilesAttributes(&a); return PROMISE_RESULT_SKIPPED; } LoadSetuid(); PromiseResult result = PROMISE_RESULT_NOOP; if (lstat(path, &oslb) == -1) /* Careful if the object is a link */ { if ((a.create) || (a.touch)) { if (!CfCreateFile(ctx, path, pp, a, &result)) { goto exit; } else { exists = (lstat(path, &oslb) != -1); } } exists = false; } else { if ((a.create) || (a.touch)) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "File '%s' exists as promised", path); } exists = true; } if ((a.havedelete) && (!exists)) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "File '%s' does not exist as promised", path); goto exit; } if (!a.havedepthsearch) /* if the search is trivial, make sure that we are in the parent dir of the leaf */ { char basedir[CF_BUFSIZE]; Log(LOG_LEVEL_DEBUG, "Direct file reference '%s', no search implied", path); snprintf(basedir, sizeof(basedir), "%s", path); if (strcmp(ReadLastNode(basedir), ".") == 0) { // Handle /. notation for deletion of directories ChopLastNode(basedir); ChopLastNode(path); } ChopLastNode(basedir); if (safe_chdir(basedir)) { Log(LOG_LEVEL_ERR, "Failed to chdir into '%s'. (chdir: '%s')", basedir, GetErrorStr()); } } /* If file or directory exists but it is not selected by body file_select * (if we have one) then just exit. But continue if it's a directory and * depth_search is on, so that we can file_select into it. */ if (exists && (a.haveselect && !SelectLeaf(ctx, path, &oslb, a.select)) && !(a.havedepthsearch && S_ISDIR(oslb.st_mode))) { goto exit; } if (stat(path, &osb) == -1) { if ((a.create) || (a.touch)) { if (!CfCreateFile(ctx, path, pp, a, &result)) { goto exit; } else { exists = true; } } else { exists = false; } } else { if (!S_ISDIR(osb.st_mode)) { if (a.havedepthsearch) { Log(LOG_LEVEL_WARNING, "depth_search (recursion) is promised for a base object '%s' that is not a directory", path); goto exit; } } exists = true; } if (a.link.link_children) { if (stat(a.link.source, &dsb) != -1) { if (!S_ISDIR(dsb.st_mode)) { Log(LOG_LEVEL_ERR, "Cannot promise to link the children of '%s' as it is not a directory!", a.link.source); goto exit; } } } /* Phase 1 - */ if (exists && ((a.havedelete) || (a.haverename) || (a.haveperms) || (a.havechange) || (a.transformer))) { lstat(path, &oslb); /* if doesn't exist have to stat again anyway */ DepthSearch(ctx, path, &oslb, 0, a, pp, oslb.st_dev, &result); /* normally searches do not include the base directory */ if (a.recursion.include_basedir) { int save_search = a.havedepthsearch; /* Handle this node specially */ a.havedepthsearch = false; DepthSearch(ctx, path, &oslb, 0, a, pp, oslb.st_dev, &result); a.havedepthsearch = save_search; } else { /* unless child nodes were repaired, set a promise kept class */ if (!IsDefinedClass(ctx, "repaired")) { cfPS(ctx, LOG_LEVEL_VERBOSE, PROMISE_RESULT_NOOP, pp, a, "Basedir '%s' not promising anything", path); } } } /* Phase 2a - copying is potentially threadable if no followup actions */ if (a.havecopy) { result = PromiseResultUpdate(result, ScheduleCopyOperation(ctx, path, a, pp)); } /* Phase 2b link after copy in case need file first */ if ((a.havelink) && (a.link.link_children)) { result = PromiseResultUpdate(result, ScheduleLinkChildrenOperation(ctx, path, a.link.source, 1, a, pp)); } else if (a.havelink) { result = PromiseResultUpdate(result, ScheduleLinkOperation(ctx, path, a.link.source, a, pp)); } /* Phase 3 - content editing */ if (a.haveedit) { if (exists) { result = PromiseResultUpdate(result, ScheduleEditOperation(ctx, path, a, pp)); } else { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Promised to edit '%s', but file does not exist", path); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } } // Once more in case a file has been created as a result of editing or copying exists = (stat(path, &osb) != -1); if (exists && (S_ISREG(osb.st_mode)) && (!a.haveselect || SelectLeaf(ctx, path, &osb, a.select))) { VerifyFileLeaf(ctx, path, &osb, a, pp, &result); } if (!exists && a.havechange) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_FAIL, pp, a, "Promised to monitor '%s' for changes, but file does not exist", path); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } exit: if (AttrHasNoAction(a)) { Log(LOG_LEVEL_INFO, "No action was requested for file '%s'. Maybe a typo in the policy?", path); } SaveSetuid(); YieldCurrentLock(thislock); ClearFilesAttributes(&a); return result; }