static void DestroyTransaction(void *ptr) { DBTxn *db_txn = (DBTxn *)ptr; UnexpectedError("Transaction object still exists when terminating thread"); if (db_txn->txn) { UnexpectedError("Transaction still open when terminating thread!"); mdb_txn_abort(db_txn->txn); } free(db_txn); }
void MonNetworkSnifferOpen(void) { char tcpbuffer[CF_BUFSIZE]; if (TCPDUMP) { struct stat statbuf; char buffer[CF_MAXVARSIZE]; sscanf(CF_TCPDUMP_COMM, "%s", buffer); if (stat(buffer, &statbuf) != -1) { if ((TCPPIPE = cf_popen(CF_TCPDUMP_COMM, "r", true)) == NULL) { TCPDUMP = false; } /* Skip first banner */ if (fgets(tcpbuffer, sizeof(tcpbuffer), TCPPIPE) == NULL) { UnexpectedError("Failed to read output from '%s'", CF_TCPDUMP_COMM); cf_pclose(TCPPIPE); TCPPIPE = NULL; TCPDUMP = false; } } else { TCPDUMP = false; } } }
/** * Trim allocated memory to the minimum needed. Free the whole struct if * deemed necessary. This function will never fail. In the unlikely event that * realloc() fails to reduce the allocated amount, then we just keep the same * memory and log an UnexpectedError. * * @param **sl in-out param, might become NULL if the struct was freed. */ void StrList_Finalise(StrList **sl) { StrList *slp = *sl; /* for clarity only */ if (slp == NULL) { return; } assert(slp->len <= slp->alloc_len); if (slp->len == 0) { free(slp); slp = NULL; } else if (slp->len < slp->alloc_len) { StrList *p = realloc(slp, sizeof(*p) + sizeof (*p->list) * slp->len); if (p == NULL) { UnexpectedError("realloc() returned error even though we asked to *reduce* allocated amount: %s", GetErrorStr()); } else { slp = p; slp->alloc_len = slp->len; } } *sl = slp; }
static void PrintFile(Attributes a, Promise *pp) { FILE *fp; char buffer[CF_BUFSIZE]; int lines = 0; if (a.report.filename == NULL) { CfOut(OUTPUT_LEVEL_VERBOSE, "", "Printfile promise was incomplete, with no filename.\n"); return; } if ((fp = fopen(a.report.filename, "r")) == NULL) { cfPS(OUTPUT_LEVEL_ERROR, CF_INTERPT, "fopen", pp, a, " !! Printing of file %s was not possible.\n", a.report.filename); return; } while ((!feof(fp)) && (lines < a.report.numlines)) { buffer[0] = '\0'; if (fgets(buffer, CF_BUFSIZE, fp) == NULL) { if (strlen(buffer)) { UnexpectedError("Failed to read line from stream"); } } CfOut(OUTPUT_LEVEL_ERROR, "", "R: %s", buffer); lines++; } fclose(fp); }
void KeepPromises(EvalContext *ctx, const Policy *policy, GenericAgentConfig *config) { if (paths_acl != NULL || classes_acl != NULL || vars_acl != NULL || literals_acl != NULL || query_acl != NULL || bundles_acl != NULL || roles_acl != NULL || SV.path_shortcuts != NULL) { UnexpectedError("ACLs are not NULL - we are probably leaking memory!"); } paths_acl = calloc(1, sizeof(*paths_acl)); classes_acl = calloc(1, sizeof(*classes_acl)); vars_acl = calloc(1, sizeof(*vars_acl)); literals_acl = calloc(1, sizeof(*literals_acl)); query_acl = calloc(1, sizeof(*query_acl)); bundles_acl = calloc(1, sizeof(*bundles_acl)); roles_acl = calloc(1, sizeof(*roles_acl)); SV.path_shortcuts = StringMapNew(); if (paths_acl == NULL || classes_acl == NULL || vars_acl == NULL || literals_acl == NULL || query_acl == NULL || bundles_acl == NULL || roles_acl == NULL || SV.path_shortcuts == NULL) { Log(LOG_LEVEL_CRIT, "calloc: %s", GetErrorStr()); exit(255); } KeepControlPromises(ctx, policy, config); KeepPromiseBundles(ctx, policy); }
void ActAsDaemon(int preserve) { int fd, maxfd; #ifdef HAVE_SETSID setsid(); #endif CloseNetwork(); CloseLog(); fflush(NULL); fd = open(NULLFILE, O_RDWR, 0); if (fd != -1) { if (dup2(fd, STDIN_FILENO) == -1) { CfOut(OUTPUT_LEVEL_ERROR, "dup2", "Could not dup"); } if (dup2(fd, STDOUT_FILENO) == -1) { CfOut(OUTPUT_LEVEL_ERROR, "dup2", "Could not dup"); } dup2(fd, STDERR_FILENO); if (fd > STDERR_FILENO) { close(fd); } } if (chdir("/")) { UnexpectedError("Failed to chdir into '/'"); } #ifdef HAVE_SYSCONF maxfd = sysconf(_SC_OPEN_MAX); #else # ifdef _POXIX_OPEN_MAX maxfd = _POSIX_OPEN_MAX; # else maxfd = 1024; # endif #endif for (fd = STDERR_FILENO + 1; fd < maxfd; ++fd) { if (fd != preserve) { close(fd); } } }
static void KeepServerRolePromise(EvalContext *ctx, const Promise *pp) { Rlist *rp; Auth *ap; if (!GetAuthPath(pp->promiser, SV.roles)) { InstallServerAuthPath(pp->promiser, &SV.roles, &SV.rolestop); } ap = GetAuthPath(pp->promiser, SV.roles); for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); if (!IsDefinedClass(ctx, cp->classes, PromiseGetNamespace(pp))) { continue; } switch (cp->rval.type) { case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { if (strcmp(cp->lval, CF_REMROLE_BODIES[REMOTE_ROLE_AUTHORIZE].lval) == 0) { PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL); continue; } } break; case RVAL_TYPE_FNCALL: UnexpectedError("Constraint of type FNCALL is invalid in this context!"); break; default: if ((strcmp(cp->lval, "comment") == 0) || (strcmp(cp->lval, "handle") == 0)) { } else { Log(LOG_LEVEL_ERR, "Right-hand side of authorize promise for '%s' should be a list", pp->promiser); } break; } } }
/** * @param len is the number of bytes to send, or 0 if buffer is a * '\0'-terminated string so strlen(buffer) can used. */ int SendTransaction(const ConnectionInfo *conn_info, const char *buffer, int len, char status) { assert(status == CF_MORE || status == CF_DONE); char work[CF_BUFSIZE] = { 0 }; int ret; if (len == 0) { len = strlen(buffer); } if (len > CF_BUFSIZE - CF_INBAND_OFFSET) { Log(LOG_LEVEL_ERR, "SendTransaction: len (%d) > %d - %d", len, CF_BUFSIZE, CF_INBAND_OFFSET); return -1; } snprintf(work, CF_INBAND_OFFSET, "%c %d", status, len); memcpy(work + CF_INBAND_OFFSET, buffer, len); Log(LOG_LEVEL_DEBUG, "SendTransaction header: %s", work); LogRaw(LOG_LEVEL_DEBUG, "SendTransaction data: ", work + CF_INBAND_OFFSET, len); switch(ConnectionInfoProtocolVersion(conn_info)) { case CF_PROTOCOL_CLASSIC: ret = SendSocketStream(ConnectionInfoSocket(conn_info), work, len + CF_INBAND_OFFSET); break; case CF_PROTOCOL_TLS: ret = TLSSend(ConnectionInfoSSL(conn_info), work, len + CF_INBAND_OFFSET); break; default: UnexpectedError("SendTransaction: ProtocolVersion %d!", ConnectionInfoProtocolVersion(conn_info)); ret = -1; } if (ret == -1) return -1; else return 0; }
static void Sniff(Item *ip_addresses, long iteration, double *cf_this) { char tcpbuffer[CF_BUFSIZE]; Log(LOG_LEVEL_VERBOSE, "Reading from tcpdump..."); memset(tcpbuffer, 0, CF_BUFSIZE); signal(SIGALRM, CfenvTimeOut); alarm(SLEEPTIME); TCPPAUSE = false; while (!feof(TCPPIPE) && !IsPendingTermination()) { if (TCPPAUSE) { break; } if (fgets(tcpbuffer, sizeof(tcpbuffer), TCPPIPE) == NULL) { UnexpectedError("Unable to read data from tcpdump; closing pipe"); cf_pclose(TCPPIPE); TCPPIPE = NULL; TCPDUMP = false; break; } if (TCPPAUSE) { break; } if (strstr(tcpbuffer, "tcpdump:")) /* Error message protect sleeptime */ { Log(LOG_LEVEL_DEBUG, "Error - '%s'", tcpbuffer); alarm(0); TCPDUMP = false; break; } AnalyzeArrival(ip_addresses, iteration, tcpbuffer, cf_this); } signal(SIGALRM, SIG_DFL); TCPPAUSE = false; fflush(TCPPIPE); }
static GenericAgentConfig *CheckOpts(int argc, char **argv) { extern char *optarg; int optindex = 0; int c; GenericAgentConfig *config = GenericAgentConfigNewDefault(AGENT_TYPE_GENDOC); if (getcwd(SOURCE_DIR, CF_BUFSIZE) == NULL) { UnexpectedError("Failed to get the pathname to the current directory"); } snprintf(OUTPUT_FILE, CF_BUFSIZE, "%scf3-Reference.texinfo", SOURCE_DIR); while ((c = getopt_long(argc, argv, "hxi:o:", OPTIONS, &optindex)) != EOF) { switch ((char) c) { case 'h': Syntax("cf-gendoc - reference manual generator", OPTIONS, HINTS, ID); exit(0); case 'x': GENERATE_XML = true; break; case 'i': strlcpy(SOURCE_DIR, optarg, CF_BUFSIZE); case 'o': strlcpy(OUTPUT_FILE, optarg, CF_BUFSIZE); break; default: Syntax("cf-gendoc - reference manual generator", OPTIONS, HINTS, ID); exit(1); } } if (argv[optind] != NULL) { CfOut(OUTPUT_LEVEL_ERROR, "", "Unexpected argument with no preceding option: %s\n", argv[optind]); } return config; }
static void PrintFile(EvalContext *ctx, Attributes a, Promise *pp) { FILE *fp; char buffer[CF_BUFSIZE]; int lines = 0; if (a.report.filename == NULL) { Log(LOG_LEVEL_VERBOSE, "Printfile promise was incomplete, with no filename."); return; } if ((fp = fopen(a.report.filename, "r")) == NULL) { cfPS(ctx, LOG_LEVEL_ERR, PROMISE_RESULT_INTERRUPTED, pp, a, "Printing of file '%s' was not possible. (fopen: %s)", a.report.filename, GetErrorStr()); return; } while ((lines < a.report.numlines)) { if (fgets(buffer, sizeof(buffer), fp) == NULL) { if (ferror(fp)) { UnexpectedError("Failed to read line from stream"); break; } else /* feof */ { break; } } Log(LOG_LEVEL_ERR, "R: %s", buffer); lines++; } fclose(fp); }
static AgentConnection *ServerConnection(const char *server, FileCopy fc, int *err) { AgentConnection *conn; *err = 0; #if !defined(__MINGW32__) signal(SIGPIPE, SIG_IGN); #endif /* !__MINGW32__ */ #if !defined(__MINGW32__) static sigset_t signal_mask; sigemptyset(&signal_mask); sigaddset(&signal_mask, SIGPIPE); pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); #endif conn = NewAgentConn(server); if (strcmp(server, "localhost") == 0) { conn->authenticated = true; return conn; } conn->authenticated = false; conn->encryption_type = CfEnterpriseOptions(); /* username of the client - say root from Windows */ #ifdef __MINGW32__ snprintf(conn->username, CF_SMALLBUF, "root"); #else GetCurrentUserName(conn->username, CF_SMALLBUF); #endif /* !__MINGW32__ */ if (conn->sd == SOCKET_INVALID) { if (!ServerConnect(conn, server, fc)) { Log(LOG_LEVEL_INFO, "No server is responding on this port"); DisconnectServer(conn); *err = -1; return NULL; } if (conn->sd < 0) /* INVALID or OFFLINE socket */ { UnexpectedError("ServerConnect() succeeded but socket descriptor is %d!", conn->sd); *err = -1; return NULL; } if (!IdentifyAgent(conn->sd)) { Log(LOG_LEVEL_ERR, "Id-authentication for '%s' failed", VFQNAME); errno = EPERM; DisconnectServer(conn); *err = -2; // auth err return NULL; } if (!AuthenticateAgent(conn, fc.trustkey)) { Log(LOG_LEVEL_ERR, "Authentication dialogue with '%s' failed", server); errno = EPERM; DisconnectServer(conn); *err = -2; // auth err return NULL; } conn->authenticated = true; return conn; } return conn; }
/** * 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); }
static void *HandleConnection(ServerConnectionState *conn) { int ret; char output[CF_BUFSIZE]; /* Set logging prefix to be the IP address for all of thread's lifetime. */ /* Should be valid for all lifetime of the thread. Just make sure that * after calling DeleteConn(), you exit right away. */ LoggingPrivContext log_ctx = { .log_hook = LogHook, .param = conn->ipaddr }; LoggingPrivSetContext(&log_ctx); if (!ThreadLock(cft_server_children)) { DeleteConn(conn); return NULL; } ACTIVE_THREADS++; if (ACTIVE_THREADS >= CFD_MAXPROCESSES) { ACTIVE_THREADS--; if (TRIES++ > MAXTRIES) /* When to say we're hung / apoptosis threshold */ { Log(LOG_LEVEL_ERR, "Server seems to be paralyzed. DOS attack? Committing apoptosis..."); FatalError(conn->ctx, "Terminating"); } if (!ThreadUnlock(cft_server_children)) { } Log(LOG_LEVEL_ERR, "Too many threads (>=%d) -- increase server maxconnections?", CFD_MAXPROCESSES); snprintf(output, CF_BUFSIZE, "BAD: Server is currently too busy -- increase maxconnections or splaytime?"); SendTransaction(conn->conn_info, output, 0, CF_DONE); DeleteConn(conn); return NULL; } else { ThreadUnlock(cft_server_children); } TRIES = 0; /* As long as there is activity, we're not stuck */ DisableSendDelays(ConnectionInfoSocket(conn->conn_info)); struct timeval tv = { .tv_sec = CONNTIMEOUT * 20, }; SetReceiveTimeout(ConnectionInfoSocket(conn->conn_info), &tv); if (ConnectionInfoConnectionStatus(conn->conn_info) != CF_CONNECTION_ESTABLISHED) { /* Decide the protocol used. */ ret = ServerTLSPeek(conn->conn_info); if (ret == -1) { DeleteConn(conn); return NULL; } } switch (ConnectionInfoProtocolVersion(conn->conn_info)) { case CF_PROTOCOL_CLASSIC: { while (BusyWithClassicConnection(conn->ctx, conn)) { } break; } case CF_PROTOCOL_TLS: { ret = ServerTLSSessionEstablish(conn); if (ret == -1) { DeleteConn(conn); return NULL; } while (BusyWithNewProtocol(conn->ctx, conn)) { } break; } default: UnexpectedError("HandleConnection: ProtocolVersion %d!", ConnectionInfoProtocolVersion(conn->conn_info)); } Log(LOG_LEVEL_INFO, "Connection closed, terminating thread", conn->ipaddr); if (!ThreadLock(cft_server_children)) { DeleteConn(conn); return NULL; } ACTIVE_THREADS--; if (!ThreadUnlock(cft_server_children)) { } DeleteConn(conn); return NULL; } /***************************************************************/ /* Toolkit/Class: conn */ /***************************************************************/ static ServerConnectionState *NewConn(EvalContext *ctx, ConnectionInfo *info) { ServerConnectionState *conn = NULL; struct sockaddr_storage addr; socklen_t size = sizeof(addr); if (getsockname(ConnectionInfoSocket(info), (struct sockaddr *)&addr, &size) == -1) { Log(LOG_LEVEL_ERR, "Could not obtain socket address. (getsockname: '%s')", GetErrorStr()); return NULL; } conn = xcalloc(1, sizeof(*conn)); conn->ctx = ctx; conn->conn_info = info; conn->id_verified = false; conn->rsa_auth = false; conn->hostname[0] = '\0'; conn->ipaddr[0] = '\0'; conn->username[0] = '\0'; conn->session_key = NULL; conn->encryption_type = 'c'; conn->maproot = false; /* Only public files (chmod o+r) accessible */ Log(LOG_LEVEL_DEBUG, "New socket %d", ConnectionInfoSocket(info)); return conn; } /***************************************************************/ static void DeleteConn(ServerConnectionState *conn) { cf_closesocket(ConnectionInfoSocket(conn->conn_info)); ConnectionInfoDestroy(&conn->conn_info); free(conn->session_key); if (conn->ipaddr != NULL) { if (!ThreadLock(cft_count)) { return; } DeleteItemMatching(&SV.connectionlist, MapAddress(conn->ipaddr)); if (!ThreadUnlock(cft_count)) { return; } } *conn = (ServerConnectionState) {0}; free(conn); }
void MountAll() { char line[CF_BUFSIZE]; FILE *pp; if (DONTDO) { CfOut(cf_verbose, "", "Promised to mount filesystem, but not on this trial run\n"); return; } else { CfOut(cf_verbose, "", " -> Attempting to mount all filesystems.\n"); } #if defined(__CYGWIN__) /* This is a shell script. Make sure it hasn't been compromised. */ struct stat sb; if (cfstat("/etc/fstab", &sb) == -1) { int fd; if ((fd = creat("/etc/fstab", 0755)) > 0) { if (write(fd, "#!/bin/sh\n\n", 10) != 10) { UnexpectedError("Failed to write to file '/etc/fstab'"); } close(fd); } else { if (sb.st_mode & (S_IWOTH | S_IWGRP)) { CfOut(cf_error, "", "File /etc/fstab was insecure. Cannot mount filesystems.\n"); return; } } } #endif SetTimeOut(RPCTIMEOUT); if ((pp = cf_popen(VMOUNTCOMM[VSYSTEMHARDCLASS], "r")) == NULL) { CfOut(cf_error, "cf_popen", "Failed to open pipe from %s\n", VMOUNTCOMM[VSYSTEMHARDCLASS]); return; } while (!feof(pp)) { if (ferror(pp)) /* abortable */ { CfOut(cf_inform, "ferror", "Error mounting filesystems\n"); break; } if (CfReadLine(line, CF_BUFSIZE, pp) == -1) { FatalError("Error in CfReadLine"); } if (ferror(pp)) /* abortable */ { CfOut(cf_inform, "ferror", "Error mounting filesystems\n"); break; } if ((strstr(line, "already mounted")) || (strstr(line, "exceeded")) || (strstr(line, "determined"))) { continue; } if (strstr(line, "not supported")) { continue; } if ((strstr(line, "denied")) || (strstr(line, "RPC"))) { CfOut(cf_error, "", "There was a mount error, trying to mount one of the filesystems on this host.\n"); break; } if ((strstr(line, "trying")) && (!strstr(line, "NFS version 2")) && (!strstr(line, "vers 3"))) { CfOut(cf_error, "", "Attempting abort because mount went into a retry loop.\n"); break; } } alarm(0); signal(SIGALRM, SIG_DFL); cf_pclose(pp); }
static int CompareResult(const char *filename, const char *prev_file) { Log(LOG_LEVEL_VERBOSE, "Comparing files %s with %s", prev_file, filename); int rtn = 0; FILE *old_fp = safe_fopen(prev_file, "r"); FILE *new_fp = safe_fopen(filename, "r"); if (old_fp && new_fp) { const char *errptr; int erroffset; pcre_extra *regex_extra = NULL; // Match timestamps and remove them. Not Y21K safe! :-) pcre *regex = pcre_compile(LOGGING_TIMESTAMP_REGEX, PCRE_MULTILINE, &errptr, &erroffset, NULL); if (!regex) { UnexpectedError("Compiling regular expression failed"); rtn = 1; } else { regex_extra = pcre_study(regex, 0, &errptr); } while (regex) { char old_line[CF_BUFSIZE]; char new_line[CF_BUFSIZE]; char *old_msg = old_line; char *new_msg = new_line; if (CfReadLine(old_line, sizeof(old_line), old_fp) <= 0) { old_msg = NULL; } if (CfReadLine(new_line, sizeof(new_line), new_fp) <= 0) { new_msg = NULL; } if (!old_msg || !new_msg) { if (old_msg != new_msg) { rtn = 1; } break; } char *index; if (pcre_exec(regex, regex_extra, old_msg, strlen(old_msg), 0, 0, NULL, 0) >= 0) { index = strstr(old_msg, ": "); if (index != NULL) { old_msg = index + 2; } } if (pcre_exec(regex, regex_extra, new_msg, strlen(new_msg), 0, 0, NULL, 0) >= 0) { index = strstr(new_msg, ": "); if (index != NULL) { new_msg = index + 2; } } if (strcmp(old_msg, new_msg) != 0) { rtn = 1; break; } } if (regex_extra) { free(regex_extra); } free(regex); } else { /* no previous file */ rtn = 1; } if (old_fp) { fclose(old_fp); } if (new_fp) { fclose(new_fp); } if (!ThreadLock(cft_count)) { Log(LOG_LEVEL_ERR, "Severe lock error when mailing in exec"); return 1; } /* replace old file with new*/ unlink(prev_file); if (!LinkOrCopy(filename, prev_file, true)) { Log(LOG_LEVEL_INFO, "Could not symlink or copy '%s' to '%s'", filename, prev_file); rtn = 1; } ThreadUnlock(cft_count); return rtn; }
void RotateFiles(char *name, int number) { int i, fd; struct stat statbuf; char from[CF_BUFSIZE], to[CF_BUFSIZE]; if (IsItemIn(ROTATED, name)) { return; } PrependItem(&ROTATED, name, NULL); if (stat(name, &statbuf) == -1) { Log(LOG_LEVEL_VERBOSE, "No access to file %s", name); return; } for (i = number - 1; i > 0; i--) { snprintf(from, CF_BUFSIZE, "%s.%d", name, i); snprintf(to, CF_BUFSIZE, "%s.%d", name, i + 1); if (rename(from, to) == -1) { Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); } snprintf(from, CF_BUFSIZE, "%s.%d.gz", name, i); snprintf(to, CF_BUFSIZE, "%s.%d.gz", name, i + 1); if (rename(from, to) == -1) { Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); } snprintf(from, CF_BUFSIZE, "%s.%d.Z", name, i); snprintf(to, CF_BUFSIZE, "%s.%d.Z", name, i + 1); if (rename(from, to) == -1) { Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); } snprintf(from, CF_BUFSIZE, "%s.%d.bz", name, i); snprintf(to, CF_BUFSIZE, "%s.%d.bz", name, i + 1); if (rename(from, to) == -1) { Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); } snprintf(from, CF_BUFSIZE, "%s.%d.bz2", name, i); snprintf(to, CF_BUFSIZE, "%s.%d.bz2", name, i + 1); if (rename(from, to) == -1) { Log(LOG_LEVEL_DEBUG, "Rename failed in RotateFiles '%s' -> '%s'", name, from); } } snprintf(to, CF_BUFSIZE, "%s.1", name); if (CopyRegularFileDisk(name, to) == false) { Log(LOG_LEVEL_DEBUG, "Copy failed in RotateFiles '%s' -> '%s'", name, to); return; } safe_chmod(to, statbuf.st_mode); if (safe_chown(to, statbuf.st_uid, statbuf.st_gid)) { UnexpectedError("Failed to chown %s", to); } safe_chmod(name, 0600); /* File must be writable to empty .. */ if ((fd = safe_creat(name, statbuf.st_mode)) == -1) { Log(LOG_LEVEL_ERR, "Failed to create new '%s' in disable(rotate). (creat: %s)", name, GetErrorStr()); } else { if (safe_chown(name, statbuf.st_uid, statbuf.st_gid)) /* NT doesn't have fchown */ { UnexpectedError("Failed to chown '%s'", name); } fchmod(fd, statbuf.st_mode); close(fd); } }
void MountAll() { FILE *pp; if (DONTDO) { Log(LOG_LEVEL_VERBOSE, "Promised to mount filesystem, but not on this trial run"); return; } else { Log(LOG_LEVEL_VERBOSE, "Attempting to mount all filesystems."); } #if defined(__CYGWIN__) /* This is a shell script. Make sure it hasn't been compromised. */ struct stat sb; if (stat("/etc/fstab", &sb) == -1) { int fd; if ((fd = creat("/etc/fstab", 0755)) > 0) { if (write(fd, "#!/bin/sh\n\n", 10) != 10) { UnexpectedError("Failed to write to file '/etc/fstab'"); } close(fd); } else { if (sb.st_mode & (S_IWOTH | S_IWGRP)) { Log(LOG_LEVEL_ERR, "File /etc/fstab was insecure. Cannot mount filesystems."); return; } } } #endif SetTimeOut(RPCTIMEOUT); const char *cmd = VMOUNTCOMM[VSYSTEMHARDCLASS]; if ((pp = cf_popen(cmd, "r", true)) == NULL) { Log(LOG_LEVEL_ERR, "Failed to open pipe from '%s'. (cf_popen: %s)", cmd, GetErrorStr()); return; } size_t line_size = CF_BUFSIZE; char *line = xmalloc(line_size); for (;;) { ssize_t res = CfReadLine(&line, &line_size, pp); if (res == -1) { if (!feof(pp)) { Log(LOG_LEVEL_ERR, "Error reading output of command '%s' (ferror: %s)", cmd, GetErrorStr()); } break; } if ((strstr(line, "already mounted")) || (strstr(line, "exceeded")) || (strstr(line, "determined"))) { continue; } if (strstr(line, "not supported")) { continue; } if ((strstr(line, "denied")) || (strstr(line, "RPC"))) { Log(LOG_LEVEL_ERR, "There was a mount error while trying to mount the filesystems" " (command '%s')", cmd); break; } if ((strstr(line, "trying")) && (!strstr(line, "NFS version 2")) && (!strstr(line, "vers 3"))) { Log(LOG_LEVEL_ERR, "Attempting filesystems mount aborted because command" " '%s' went into a retry loop", cmd); break; } } free(line); alarm(0); signal(SIGALRM, SIG_DFL); cf_pclose(pp); }
/** * @param len is the number of bytes to send, or 0 if buffer is a * '\0'-terminated string so strlen(buffer) can used. * @return -1 in case of error or connection closed * (also currently returns 0 for success but don't count on it) * @NOTE #buffer can't be of zero length, our protocol * does not allow empty transactions! The reason is that * ReceiveTransaction() can't differentiate between that * and connection closed. * @NOTE (len <= CF_BUFSIZE - CF_INBAND_OFFSET) */ int SendTransaction(const ConnectionInfo *conn_info, const char *buffer, int len, char status) { assert(status == CF_MORE || status == CF_DONE); char work[CF_BUFSIZE] = { 0 }; int ret; if (len == 0) { len = strlen(buffer); } /* Not allowed to send zero-payload packets, because (ReceiveTransaction() == 0) currently means connection closed. */ assert(len > 0); if (len > CF_BUFSIZE - CF_INBAND_OFFSET) { Log(LOG_LEVEL_ERR, "SendTransaction: len (%d) > %d - %d", len, CF_BUFSIZE, CF_INBAND_OFFSET); return -1; } snprintf(work, CF_INBAND_OFFSET, "%c %d", status, len); memcpy(work + CF_INBAND_OFFSET, buffer, len); Log(LOG_LEVEL_DEBUG, "SendTransaction header: %s", work); LogRaw(LOG_LEVEL_DEBUG, "SendTransaction data: ", work + CF_INBAND_OFFSET, len); switch(conn_info->protocol) { case CF_PROTOCOL_CLASSIC: ret = SendSocketStream(conn_info->sd, work, len + CF_INBAND_OFFSET); break; case CF_PROTOCOL_TLS: ret = TLSSend(conn_info->ssl, work, len + CF_INBAND_OFFSET); if (ret <= 0) { ret = -1; } break; default: UnexpectedError("SendTransaction: ProtocolVersion %d!", conn_info->protocol); ret = -1; } if (ret == -1) { return -1; /* error */ } else { /* SSL_MODE_AUTO_RETRY guarantees no partial writes. */ assert(ret == len + CF_INBAND_OFFSET); return 0; } }
/** * @return 0 in case of socket closed, -1 in case of other error, or * >0 the number of bytes read. */ int ReceiveTransaction(const ConnectionInfo *conn_info, char *buffer, int *more) { char proto[CF_INBAND_OFFSET + 1] = { 0 }; char status = 'x'; unsigned int len = 0; int ret; /* Get control channel. */ switch(ConnectionInfoProtocolVersion(conn_info)) { case CF_PROTOCOL_CLASSIC: ret = RecvSocketStream(ConnectionInfoSocket(conn_info), proto, CF_INBAND_OFFSET); break; case CF_PROTOCOL_TLS: ret = TLSRecv(ConnectionInfoSSL(conn_info), proto, CF_INBAND_OFFSET); break; default: UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", ConnectionInfoProtocolVersion(conn_info)); ret = -1; } if (ret == -1 || ret == 0) return ret; LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction header: ", proto, ret); ret = sscanf(proto, "%c %u", &status, &len); if (ret != 2) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: Bad packet -- bogus header: %s", proto); return -1; } if (len > CF_BUFSIZE - CF_INBAND_OFFSET) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: Bad packet -- too long (len=%d)", len); return -1; } if (status != CF_MORE && status != CF_DONE) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: Bad packet -- bogus header (more='%c')", status); return -1; } if (more != NULL) { switch (status) { case CF_MORE: *more = true; break; case CF_DONE: *more = false; break; default: ProgrammingError("Unreachable, " "bogus headers have already been checked!"); } } /* Get data. */ switch(ConnectionInfoProtocolVersion(conn_info)) { case CF_PROTOCOL_CLASSIC: ret = RecvSocketStream(ConnectionInfoSocket(conn_info), buffer, len); break; case CF_PROTOCOL_TLS: ret = TLSRecv(ConnectionInfoSSL(conn_info), buffer, len); break; default: UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", ConnectionInfoProtocolVersion(conn_info)); ret = -1; } LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction data: ", buffer, ret); return ret; }
static bool CFTestD_BusyLoop( ServerConnectionState *conn, CFTestD_Config *config) { char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = ""; char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = ""; const int received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1) { /* Already Log()ged in case of error. */ return false; } if (received > CF_BUFSIZE - 1) { UnexpectedError("Received transaction of size %d", received); return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission (of size %d)", received); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } // See BusyWithNewProtocol() in server_tls.c for how to add other commands switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { return CFTestD_ProtocolError(conn, recvbuffer, sendbuffer); } if (CFTestD_GetServerQuery(conn, recvbuffer, config)) { return true; } break; } case PROTOCOL_COMMAND_BAD: default: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } return CFTestD_ProtocolError(conn, recvbuffer, sendbuffer); }
void KeepQueryAccessPromise(EvalContext *ctx, const Promise *pp, char *type) { Rlist *rp; Auth *ap, *dp; if (!GetAuthPath(pp->promiser, SV.varadmit)) { InstallServerAuthPath(pp->promiser, &SV.varadmit, &SV.varadmittop); } RegisterLiteralServerData(ctx, pp->promiser, pp); if (!GetAuthPath(pp->promiser, SV.vardeny)) { InstallServerAuthPath(pp->promiser, &SV.vardeny, &SV.vardenytop); } ap = GetAuthPath(pp->promiser, SV.varadmit); dp = GetAuthPath(pp->promiser, SV.vardeny); if (strcmp(type, "query") == 0) { ap->literal = true; } for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); if (!IsDefinedClass(ctx, cp->classes, PromiseGetNamespace(pp))) { continue; } switch (cp->rval.type) { case RVAL_TYPE_SCALAR: if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_ENCRYPTED].lval) == 0) { ap->encrypt = true; } break; case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_ADMIT].lval) == 0) { PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL); continue; } if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_DENY].lval) == 0) { PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL); continue; } if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_MAPROOT].lval) == 0) { PrependItem(&(ap->maproot), RlistScalarValue(rp), NULL); continue; } } break; default: UnexpectedError("Unknown constraint type!"); break; } } }
void KeepLiteralAccessPromise(EvalContext *ctx, const Promise *pp, char *type) { Rlist *rp; Auth *ap = NULL, *dp = NULL; const char *handle = PromiseGetHandle(pp); if ((handle == NULL) && (strcmp(type,"literal") == 0)) { Log(LOG_LEVEL_ERR, "Access to literal server data requires you to define a promise handle for reference"); return; } if (strcmp(type, "literal") == 0) { Log(LOG_LEVEL_VERBOSE,"Looking at literal access promise '%s', type '%s'", pp->promiser, type); if (!GetAuthPath(handle, SV.varadmit)) { InstallServerAuthPath(handle, &SV.varadmit, &SV.varadmittop); } if (!GetAuthPath(handle, SV.vardeny)) { InstallServerAuthPath(handle, &SV.vardeny, &SV.vardenytop); } RegisterLiteralServerData(ctx, handle, pp); ap = GetAuthPath(handle, SV.varadmit); dp = GetAuthPath(handle, SV.vardeny); ap->literal = true; } else { Log(LOG_LEVEL_VERBOSE,"Looking at context/var access promise '%s', type '%s'", pp->promiser, type); if (!GetAuthPath(pp->promiser, SV.varadmit)) { InstallServerAuthPath(pp->promiser, &SV.varadmit, &SV.varadmittop); } if (!GetAuthPath(pp->promiser, SV.vardeny)) { InstallServerAuthPath(pp->promiser, &SV.vardeny, &SV.vardenytop); } if (strcmp(type, "context") == 0) { ap = GetAuthPath(pp->promiser, SV.varadmit); dp = GetAuthPath(pp->promiser, SV.vardeny); ap->classpattern = true; } if (strcmp(type, "variable") == 0) { ap = GetAuthPath(pp->promiser, SV.varadmit); // Allow the promiser (preferred) as well as handle as variable name dp = GetAuthPath(pp->promiser, SV.vardeny); ap->variable = true; } } for (size_t i = 0; i < SeqLength(pp->conlist); i++) { Constraint *cp = SeqAt(pp->conlist, i); if (!IsDefinedClass(ctx, cp->classes, PromiseGetNamespace(pp))) { continue; } switch (cp->rval.type) { case RVAL_TYPE_SCALAR: if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_ENCRYPTED].lval) == 0) { ap->encrypt = true; } break; case RVAL_TYPE_LIST: for (rp = (Rlist *) cp->rval.item; rp != NULL; rp = rp->next) { if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_ADMIT].lval) == 0) { PrependItem(&(ap->accesslist), RlistScalarValue(rp), NULL); continue; } if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_DENY].lval) == 0) { PrependItem(&(dp->accesslist), RlistScalarValue(rp), NULL); continue; } if (strcmp(cp->lval, CF_REMACCESS_BODIES[REMOTE_ACCESS_MAPROOT].lval) == 0) { PrependItem(&(ap->maproot), RlistScalarValue(rp), NULL); continue; } } break; default: UnexpectedError("Unknown constraint type!"); break; } } }
bool BusyWithNewProtocol(EvalContext *ctx, ServerConnectionState *conn) { /* The CF_BUFEXT extra space is there to ensure we're not *reading* out of * bounds in commands that carry extra binary arguments, like MD5. */ char recvbuffer[CF_BUFSIZE + CF_BUFEXT] = { 0 }; /* This size is the max we can SendTransaction(). */ char sendbuffer[CF_BUFSIZE - CF_INBAND_OFFSET] = { 0 }; char filename[CF_BUFSIZE + 1]; /* +1 for appending slash sometimes */ ServerFileGetState get_args = { 0 }; /* We already encrypt because of the TLS layer, no need to encrypt more. */ const int encrypted = 0; /* Legacy stuff only for old protocol. */ assert(conn->rsa_auth == 1); assert(conn->user_data_set == 1); /* Receive up to CF_BUFSIZE - 1 bytes. */ const int received = ReceiveTransaction(conn->conn_info, recvbuffer, NULL); if (received == -1) { /* Already logged in case of error. */ return false; } if (received > CF_BUFSIZE - 1) { UnexpectedError("Received transaction of size %d", received); return false; } if (strlen(recvbuffer) == 0) { Log(LOG_LEVEL_WARNING, "Got NULL transmission (of size %d)", received); return true; } /* Don't process request if we're signalled to exit. */ if (IsPendingTermination()) { Log(LOG_LEVEL_VERBOSE, "Server must exit, closing connection"); return false; } /* TODO break recvbuffer here: command, param1, param2 etc. */ switch (GetCommandNew(recvbuffer)) { case PROTOCOL_COMMAND_EXEC: { const size_t EXEC_len = strlen(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC]); /* Assert recvbuffer starts with EXEC. */ assert(strncmp(PROTOCOL_NEW[PROTOCOL_COMMAND_EXEC], recvbuffer, EXEC_len) == 0); char *args = &recvbuffer[EXEC_len]; args += strspn(args, " \t"); /* bypass spaces */ Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "EXEC", args); bool b = DoExec2(ctx, conn, args, sendbuffer, sizeof(sendbuffer)); /* In the end we might keep the connection open (return true) to be * ready for next requests, but we must always send the TERMINATOR * string so that the client can close the connection at will. */ Terminate(conn->conn_info); return b; } case PROTOCOL_COMMAND_VERSION: snprintf(sendbuffer, sizeof(sendbuffer), "OK: %s", Version()); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; case PROTOCOL_COMMAND_GET: { int ret = sscanf(recvbuffer, "GET %d %[^\n]", &(get_args.buf_size), filename); if (ret != 2 || get_args.buf_size <= 0 || get_args.buf_size > CF_BUFSIZE) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "GET", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "GET", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to GET: %s", filename); RefuseAccess(conn, recvbuffer); return true; } memset(sendbuffer, 0, sizeof(sendbuffer)); if (get_args.buf_size >= CF_BUFSIZE) { get_args.buf_size = 2048; } /* TODO eliminate! */ get_args.conn = conn; get_args.encrypt = false; get_args.replybuff = sendbuffer; get_args.replyfile = filename; CfGetFile(&get_args); return true; } case PROTOCOL_COMMAND_OPENDIR: { memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "OPENDIR %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "OPENDIR", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { goto protocol_error; } /* OPENDIR *must* be directory. */ PathAppendTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "OPENDIR", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to OPENDIR: %s", filename); RefuseAccess(conn, recvbuffer); return true; } CfOpenDirectory(conn, sendbuffer, filename); return true; } case PROTOCOL_COMMAND_SYNCH: { long time_no_see = 0; memset(filename, 0, sizeof(filename)); int ret = sscanf(recvbuffer, "SYNCH %ld STAT %[^\n]", &time_no_see, filename); if (ret != 2 || filename[0] == '\0') { goto protocol_error; } time_t tloc = time(NULL); if (tloc == -1) { /* Should never happen. */ Log(LOG_LEVEL_ERR, "Couldn't read system clock. (time: %s)", GetErrorStr()); SendTransaction(conn->conn_info, "BAD: clocks out of synch", 0, CF_DONE); return true; } time_t trem = (time_t) time_no_see; int drift = (int) (tloc - trem); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "STAT", filename); /* sizeof()-1 because we need one extra byte for appending '/' afterwards. */ size_t zret = ShortcutsExpand(filename, sizeof(filename) - 1, SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename) - 1); if (zret == (size_t) -1) { goto protocol_error; } if (IsDirReal(filename) == 1) { PathAppendTrailingSlash(filename, strlen(filename)); } else { PathRemoveTrailingSlash(filename, strlen(filename)); } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "STAT", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to STAT: %s", filename); RefuseAccess(conn, recvbuffer); return true; } if (DENYBADCLOCKS && (drift * drift > CLOCK_DRIFT * CLOCK_DRIFT)) { snprintf(sendbuffer, sizeof(sendbuffer), "BAD: Clocks are too far unsynchronized %ld/%ld", (long) tloc, (long) trem); Log(LOG_LEVEL_INFO, "denybadclocks %s", sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } else { Log(LOG_LEVEL_DEBUG, "Clocks were off by %ld", (long) tloc - (long) trem); StatFile(conn, sendbuffer, filename); } return true; } case PROTOCOL_COMMAND_MD5: { int ret = sscanf(recvbuffer, "MD5 %[^\n]", filename); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "MD5", filename); /* TODO batch all the following in one function since it's very * similar in all of GET, OPENDIR and STAT. */ size_t zret = ShortcutsExpand(filename, sizeof(filename), SV.path_shortcuts, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))); if (zret == (size_t) -1) { goto protocol_error; } zret = PreprocessRequestPath(filename, sizeof(filename)); if (zret == (size_t) -1) { goto protocol_error; } PathRemoveTrailingSlash(filename, strlen(filename)); Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Translated to:", "MD5", filename); if (acl_CheckPath(paths_acl, filename, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to file: %s", filename); RefuseAccess(conn, recvbuffer); return true; } assert(CF_DEFAULT_DIGEST_LEN <= EVP_MAX_MD_SIZE); unsigned char digest[EVP_MAX_MD_SIZE + 1]; assert(CF_BUFSIZE + CF_SMALL_OFFSET + CF_DEFAULT_DIGEST_LEN <= sizeof(recvbuffer)); memcpy(digest, recvbuffer + strlen(recvbuffer) + CF_SMALL_OFFSET, CF_DEFAULT_DIGEST_LEN); CompareLocalHash(filename, digest, sendbuffer); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); return true; } case PROTOCOL_COMMAND_VAR: { char var[256]; int ret = sscanf(recvbuffer, "VAR %255[^\n]", var); if (ret != 1) { goto protocol_error; } /* TODO if this is literals_acl, then when should I check vars_acl? */ if (acl_CheckExact(literals_acl, var, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to variable: %s", var); RefuseAccess(conn, recvbuffer); return true; } GetServerLiteral(ctx, conn, sendbuffer, recvbuffer, encrypted); return true; } case PROTOCOL_COMMAND_CONTEXT: { char client_regex[256]; int ret = sscanf(recvbuffer, "CONTEXT %255[^\n]", client_regex); if (ret != 1) { goto protocol_error; } Log(LOG_LEVEL_VERBOSE, "%14s %7s %s", "Received:", "CONTEXT", client_regex); /* WARNING: this comes from legacy code and must be killed if we care * about performance. We should not accept regular expressions from * the client, but this will break backwards compatibility. * * I replicated the code in raw form here to emphasize complexity, * it's the only *slow* command currently in the protocol. */ Item *persistent_classes = ListPersistentClasses(); Item *matched_classes = NULL; /* For all persistent classes */ for (Item *ip = persistent_classes; ip != NULL; ip = ip->next) { const char *class_name = ip->name; /* Does this class match the regex the client sent? */ if (StringMatchFull(client_regex, class_name)) { /* Is this class allowed to be given to the specific * host, according to the regexes in the ACLs? */ if (acl_CheckRegex(classes_acl, class_name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info)), NULL) == true) { Log(LOG_LEVEL_DEBUG, "Access granted to class: %s", class_name); PrependItem(&matched_classes, class_name, NULL); } } } if (matched_classes == NULL) { Log(LOG_LEVEL_INFO, "No allowed classes for remoteclassesmatching: %s", client_regex); RefuseAccess(conn, recvbuffer); return true; } ReplyServerContext(conn, encrypted, matched_classes); return true; } case PROTOCOL_COMMAND_QUERY: { char query[256], name[128]; int ret1 = sscanf(recvbuffer, "QUERY %255[^\n]", query); int ret2 = sscanf(recvbuffer, "QUERY %127s", name); if (ret1 != 1 || ret2 != 1) { goto protocol_error; } if (acl_CheckExact(query_acl, name, conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to query: %s", query); RefuseAccess(conn, recvbuffer); return true; } if (GetServerQuery(conn, recvbuffer, encrypted)) { return true; } break; } case PROTOCOL_COMMAND_CALL_ME_BACK: /* Server side, handing the collect call off to cf-hub. */ if (acl_CheckExact(query_acl, "collect_calls", conn->ipaddr, conn->revdns, KeyPrintableHash(ConnectionInfoKey(conn->conn_info))) == false) { Log(LOG_LEVEL_INFO, "access denied to Call-Collect, check the ACL for class: collect_calls"); return false; } ReceiveCollectCall(conn); /* On success that returned true; otherwise, it did all * relevant Log()ging. Either way, we're no longer busy with * it and our caller can close the connection: */ return false; case PROTOCOL_COMMAND_BAD: Log(LOG_LEVEL_WARNING, "Unexpected protocol command: %s", recvbuffer); } /* We should only reach this point if something went really bad, and * close connection. In all other cases (like access denied) connection * shouldn't be closed. * TODO So we need this function to return more than true/false, because * now we return true even when access is denied! E.g. return -1 for * error, 0 on success, 1 on access denied. It can be an option if * connection will close on denial. */ protocol_error: strcpy(sendbuffer, "BAD: Request denied"); SendTransaction(conn->conn_info, sendbuffer, 0, CF_DONE); Log(LOG_LEVEL_INFO, "Closing connection due to illegal request: %s", recvbuffer); return false; }
PromiseResult VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return PROMISE_RESULT_NOOP; } Attributes a = { {0} }; // More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); a.classes = GetClassDefinitionConstraints(ctx, pp); VarRef *ref = VarRefParseFromBundle(pp->promiser, PromiseGetBundle(pp)); if (strcmp("meta", pp->parent_promise_type->name) == 0) { VarRefSetMeta(ref, true); } DataType existing_value_type = CF_DATA_TYPE_NONE; const void *const existing_value = IsExpandable(pp->promiser) ? NULL : EvalContextVariableGet(ctx, ref, &existing_value_type); PromiseResult result = PROMISE_RESULT_NOOP; Rval rval = opts.cp_save->rval; if (rval.item != NULL) { DataType data_type = DataTypeFromString(opts.cp_save->lval); if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) { FnCall *fp = RvalFnCallValue(rval); const FnCallType *fn = FnCallTypeGet(fp->name); if (!fn) { assert(false && "Canary: should have been caught before this point"); FatalError(ctx, "While setting variable '%s' in bundle '%s', unknown function '%s'", pp->promiser, PromiseGetBundle(pp)->name, fp->name); } if (fn->dtype != DataTypeFromString(opts.cp_save->lval)) { FatalError(ctx, "While setting variable '%s' in bundle '%s', variable declared type '%s' but function '%s' returns type '%s'", pp->promiser, PromiseGetBundle(pp)->name, opts.cp_save->lval, fp->name, DataTypeToString(fn->dtype)); } if (existing_value_type != CF_DATA_TYPE_NONE) { // Already did this VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } FnCallResult res = FnCallEvaluate(ctx, PromiseGetPolicy(pp), fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); bool malformed = false, misprint = false; if (strcmp(opts.cp_save->lval, "int") == 0) { long int asint = IntFromString(opts.cp_save->rval.item); if (asint == CF_NOINT) { malformed = true; } else if (0 > BufferPrintf(conv, "%ld", asint)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else if (strcmp(opts.cp_save->lval, "real") == 0) { double real_value; if (!DoubleFromString(opts.cp_save->rval.item, &real_value)) { malformed = true; } else if (0 > BufferPrintf(conv, "%lf", real_value)) { misprint = true; } else { rval = RvalNew(BufferData(conv), opts.cp_save->rval.type); } } else { rval = RvalCopy(opts.cp_save->rval); } BufferDestroy(conv); if (malformed) { /* Arises when opts->cp_save->rval.item isn't yet expanded. */ /* Has already been logged by *FromString */ VarRefDestroy(ref); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else if (misprint) { /* Even though no problems with memory allocation can * get here, there might be other problems. */ UnexpectedError("Problems writing to buffer"); VarRefDestroy(ref); return PROMISE_RESULT_NOOP; } else if (rval.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(rval); RlistFlatten(ctx, &rval_list); rval.item = rval_list; } } if (Epimenides(ctx, PromiseGetBundle(pp)->ns, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) { Log(LOG_LEVEL_ERR, "Variable '%s' contains itself indirectly - an unkeepable promise", pp->promiser); exit(EXIT_FAILURE); } else { /* See if the variable needs recursively expanding again */ Rval returnval = EvaluateFinalRval(ctx, PromiseGetPolicy(pp), ref->ns, ref->scope, rval, true, pp); RvalDestroy(rval); // freed before function exit rval = returnval; } if (existing_value_type != CF_DATA_TYPE_NONE) { if (!opts.ok_redefine) /* only on second iteration, else we ignore broken promises */ { if (THIS_AGENT_TYPE == AGENT_TYPE_COMMON && !CompareRval(existing_value, DataTypeToRvalType(existing_value_type), rval.item, rval.type)) { switch (rval.type) { case RVAL_TYPE_SCALAR: Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant scalar '%s', was '%s' now '%s'", pp->promiser, (const char *)existing_value, RvalScalarValue(rval)); PromiseRef(LOG_LEVEL_VERBOSE, pp); break; case RVAL_TYPE_LIST: { Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant list '%s'", pp->promiser); Writer *w = StringWriter(); RlistWrite(w, existing_value); char *oldstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, "Old value '%s'", oldstr); free(oldstr); w = StringWriter(); RlistWrite(w, rval.item); char *newstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, " New value '%s'", newstr); free(newstr); PromiseRef(LOG_LEVEL_VERBOSE, pp); } break; case RVAL_TYPE_CONTAINER: case RVAL_TYPE_FNCALL: case RVAL_TYPE_NOPROMISEE: break; } } RvalDestroy(rval); VarRefDestroy(ref); return result; } } if (IsCf3VarString(pp->promiser)) { // Unexpanded variables, we don't do anything with RvalDestroy(rval); VarRefDestroy(ref); return result; } if (!IsValidVariableName(pp->promiser)) { Log(LOG_LEVEL_ERR, "Variable identifier contains illegal characters"); PromiseRef(LOG_LEVEL_ERR, pp); RvalDestroy(rval); VarRefDestroy(ref); return result; } if (rval.type == RVAL_TYPE_LIST) { if (opts.drop_undefined) { for (Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { if (IsNakedVar(RlistScalarValue(rp), '@')) { free(rp->val.item); rp->val.item = xstrdup(CF_NULL_VALUE); } } } for (const Rlist *rp = RvalRlistValue(rval); rp; rp = rp->next) { switch (rp->val.type) { case RVAL_TYPE_SCALAR: break; default: // Cannot assign variable because value is a list containing a non-scalar item VarRefDestroy(ref); RvalDestroy(rval); return result; } } } if (ref->num_indices > 0) { if (data_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); Log(LOG_LEVEL_ERR, "Cannot assign a container to an indexed variable name '%s'. Should be assigned to '%s' instead", lval_str, ref->lval); free(lval_str); VarRefDestroy(ref); RvalDestroy(rval); return result; } else { DataType existing_type = CF_DATA_TYPE_NONE; VarRef *base_ref = VarRefCopyIndexless(ref); if (EvalContextVariableGet(ctx, ref, &existing_type) && existing_type == CF_DATA_TYPE_CONTAINER) { char *lval_str = VarRefToString(ref, true); char *base_ref_str = VarRefToString(base_ref, true); Log(LOG_LEVEL_ERR, "Cannot assign value to indexed variable name '%s', because a container is already assigned to the base name '%s'", lval_str, base_ref_str); free(lval_str); free(base_ref_str); VarRefDestroy(base_ref); VarRefDestroy(ref); RvalDestroy(rval); return result; } VarRefDestroy(base_ref); } } DataType required_datatype = DataTypeFromString(opts.cp_save->lval); if (rval.type != DataTypeToRvalType(required_datatype)) { char *ref_str = VarRefToString(ref, true); char *value_str = RvalToString(rval); Log(LOG_LEVEL_ERR, "Variable '%s' expected a variable of type '%s', but was given incompatible value '%s'", ref_str, DataTypeToString(required_datatype), value_str); PromiseRef(LOG_LEVEL_ERR, pp); free(ref_str); free(value_str); VarRefDestroy(ref); RvalDestroy(rval); return PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } if (!EvalContextVariablePut(ctx, ref, rval.item, required_datatype, "source=promise")) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", ref->scope, pp->promiser); PromiseRef(LOG_LEVEL_VERBOSE, pp); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } else { Rlist *promise_meta = PromiseGetConstraintAsList(ctx, "meta", pp); if (promise_meta) { StringSet *class_meta = EvalContextVariableTags(ctx, ref); Buffer *print; for (const Rlist *rp = promise_meta; rp; rp = rp->next) { StringSetAdd(class_meta, xstrdup(RlistScalarValue(rp))); print = StringSetToBuffer(class_meta, ','); Log(LOG_LEVEL_DEBUG, "Added tag %s to class %s, tags now [%s]", RlistScalarValue(rp), pp->promiser, BufferData(print)); BufferDestroy(print); } } } } else { Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); Log(LOG_LEVEL_ERR, "Rule from %s at/before line %llu", PromiseGetBundle(pp)->source_path, (unsigned long long)opts.cp_save->offset.line); result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL); } /* * FIXME: Variable promise are exempt from normal evaluation logic still, so * they are not pushed to evaluation stack before being evaluated. Due to * this reason, we cannot call cfPS here to set classes, as it will error * out with ProgrammingError. * * In order to support 'classes' body for variables as well, we call * ClassAuditLog explicitly. */ ClassAuditLog(ctx, pp, a, result); VarRefDestroy(ref); RvalDestroy(rval); return result; }
/** * Generic *at function. * @param dirfd File descriptor pointing to directory to do lookup in. * AT_FDCWD constant means to look in current directory. * @param func Function to call while in the directory. * @param cleanup Function to call if we need to clean up because of a failed call. * @param data Private data for the supplied functions. */ int generic_at_function(int dirfd, int (*func)(void *data), void (*cleanup)(void *data), void *data) { int cwd; int mutex_err; int saved_errno; int fchdir_ret; mutex_err = pthread_mutex_lock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when locking CHDIR_LOCK. Should never happen. (pthread_mutex_lock: '%s')", GetErrorStrFromCode(mutex_err)); } if (dirfd != AT_FDCWD) { cwd = open(".", O_RDONLY); if (cwd < 0) { mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } return -1; } if (fchdir(dirfd) < 0) { close(cwd); mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } return -1; } } int result = func(data); saved_errno = errno; if (dirfd != AT_FDCWD) { fchdir_ret = fchdir(cwd); close(cwd); } mutex_err = pthread_mutex_unlock(&CHDIR_LOCK); if (mutex_err) { UnexpectedError("Error when unlocking CHDIR_LOCK. Should never happen. (pthread_mutex_unlock: '%s')", GetErrorStrFromCode(mutex_err)); } if (dirfd != AT_FDCWD) { if (fchdir_ret < 0) { cleanup(data); Log(LOG_LEVEL_WARNING, "Could not return to original working directory in '%s'. " "Things may not behave as expected. (fchdir: '%s')", __FUNCTION__, GetErrorStr()); return -1; } } errno = saved_errno; return result; }
static void RandomSeed(void) { /* 1. Seed the weak C PRNGs. */ /* Mix various stuff. */ pid_t pid = getpid(); size_t fqdn_len = strlen(VFQNAME) > 0 ? strlen(VFQNAME) : 1; time_t start_time = CFSTARTTIME; time_t now = time(NULL); srand((unsigned) pid * fqdn_len * start_time * now); srand48((long) (pid * fqdn_len * start_time * now)); /* 2. Seed the strong OpenSSL PRNG. */ /* randseed file is written by cf-key. */ char randfile[CF_BUFSIZE]; snprintf(randfile, CF_BUFSIZE, "%s%crandseed", CFWORKDIR, FILE_SEPARATOR); Log(LOG_LEVEL_VERBOSE, "Looking for a source of entropy in '%s'", randfile); if (!RAND_load_file(randfile, -1)) { Log(LOG_LEVEL_VERBOSE, "Could not read sufficient randomness from '%s'", randfile); } #ifndef __MINGW32__ /* windows may hang */ RAND_poll(); #else RAND_screen(); #endif /* We should have had enough entropy by now. Else we print a message and * use non-crypto-safe random data. */ if (RAND_status() != 1) { /* TODO raise to LOG_LEVEL_WARNING? */ Log(LOG_LEVEL_INFO, "PRNG hasn't been seeded enough, using some pseudo-random bytes for seed!"); Log(LOG_LEVEL_INFO, "A workaround is to copy 1KB of random bytes to '%s'", randfile); unsigned char rand_buf[128]; for (size_t i = 0; i < sizeof(rand_buf); i++) { rand_buf[i] = rand() % 256; } RAND_seed(rand_buf, sizeof(rand_buf)); /* If we *still* not have enough entropy, then things will be failing * all over the place. Should never happen because of the rand() * buffer above which should be enough for all cases. */ if (RAND_status() != 1) { UnexpectedError("Low entropy, crypto operations will fail! " "See verbose log and report which platform you are using."); } } }
static void FindV6InterfacesInfo(void) { FILE *pp = NULL; char buffer[CF_BUFSIZE]; /* Whatever the manuals might say, you cannot get IPV6 interface configuration from the ioctls. This seems to be implemented in a non standard way across OSes BSDi has done getifaddrs(), solaris 8 has a new ioctl, Stevens book shows the suggestion which has not been implemented... */ CfOut(OUTPUT_LEVEL_VERBOSE, "", "Trying to locate my IPv6 address\n"); #if defined(__CYGWIN__) /* NT cannot do this */ return; #elif defined(__hpux) if ((pp = cf_popen("/usr/sbin/ifconfig -a", "r")) == NULL) { CfOut(OUTPUT_LEVEL_VERBOSE, "", "Could not find interface info\n"); return; } #elif defined(_AIX) if ((pp = cf_popen("/etc/ifconfig -a", "r")) == NULL) { CfOut(OUTPUT_LEVEL_VERBOSE, "", "Could not find interface info\n"); return; } #else if ((pp = cf_popen("/sbin/ifconfig -a", "r")) == NULL) { CfOut(OUTPUT_LEVEL_VERBOSE, "", "Could not find interface info\n"); return; } #endif /* Don't know the output format of ifconfig on all these .. hope for the best*/ while (!feof(pp)) { buffer[0] = '\0'; if (fgets(buffer, CF_BUFSIZE, pp) == NULL) { if (strlen(buffer)) { UnexpectedError("Failed to read line from stream"); } } if (ferror(pp)) /* abortable */ { break; } if (strcasestr(buffer, "inet6")) { Item *ip, *list = NULL; char *sp; list = SplitStringAsItemList(buffer, ' '); for (ip = list; ip != NULL; ip = ip->next) { for (sp = ip->name; *sp != '\0'; sp++) { if (*sp == '/') /* Remove CIDR mask */ { *sp = '\0'; } } if ((IsIPV6Address(ip->name)) && ((strcmp(ip->name, "::1") != 0))) { CfOut(OUTPUT_LEVEL_VERBOSE, "", "Found IPv6 address %s\n", ip->name); AppendItem(&IPADDRESSES, ip->name, ""); HardClass(ip->name); } } DeleteItemList(list); } } cf_pclose(pp); }
void VerifyVarPromise(EvalContext *ctx, const Promise *pp, bool allow_duplicates) { ConvergeVariableOptions opts = CollectConvergeVariableOptions(ctx, pp, allow_duplicates); if (!opts.should_converge) { return; } char *scope = NULL; if (strcmp("meta", pp->parent_promise_type->name) == 0) { scope = StringConcatenate(2, PromiseGetBundle(pp)->name, "_meta"); } else { scope = xstrdup(PromiseGetBundle(pp)->name); } //More consideration needs to be given to using these //a.transaction = GetTransactionConstraints(pp); Attributes a = { {0} }; a.classes = GetClassDefinitionConstraints(ctx, pp); Rval existing_var_rval; DataType existing_var_type = DATA_TYPE_NONE; EvalContextVariableGet(ctx, (VarRef) { NULL, scope, pp->promiser }, &existing_var_rval, &existing_var_type); Buffer *qualified_scope = BufferNew(); int result = 0; if (strcmp(PromiseGetNamespace(pp), "default") == 0) { result = BufferSet(qualified_scope, scope, strlen(scope)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } else { if (strchr(scope, ':') == NULL) { result = BufferPrintf(qualified_scope, "%s:%s", PromiseGetNamespace(pp), scope); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } else { result = BufferSet(qualified_scope, scope, strlen(scope)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); return; } } } PromiseResult promise_result; Rval rval = opts.cp_save->rval; if (rval.item != NULL) { FnCall *fp = (FnCall *) rval.item; if (opts.cp_save->rval.type == RVAL_TYPE_FNCALL) { if (existing_var_type != DATA_TYPE_NONE) { // Already did this free(scope); BufferDestroy(&qualified_scope); return; } FnCallResult res = FnCallEvaluate(ctx, fp, pp); if (res.status == FNCALL_FAILURE) { /* We do not assign variables to failed fn calls */ RvalDestroy(res.rval); free(scope); BufferDestroy(&qualified_scope); return; } else { rval = res.rval; } } else { Buffer *conv = BufferNew(); if (strcmp(opts.cp_save->lval, "int") == 0) { result = BufferPrintf(conv, "%ld", IntFromString(opts.cp_save->rval.item)); if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&qualified_scope); BufferDestroy(&conv); return; } rval = RvalCopy((Rval) {(char *)BufferData(conv), opts.cp_save->rval.type}); } else if (strcmp(opts.cp_save->lval, "real") == 0) { double real_value = 0.0; if (DoubleFromString(opts.cp_save->rval.item, &real_value)) { result = BufferPrintf(conv, "%lf", real_value); } else { result = BufferPrintf(conv, "(double conversion error)"); } if (result < 0) { /* * Even though there will be no problems with memory allocation, there * might be other problems. */ UnexpectedError("Problems writing to buffer"); free(scope); BufferDestroy(&conv); BufferDestroy(&qualified_scope); return; } rval = RvalCopy((Rval) {(char *)BufferData(conv), opts.cp_save->rval.type}); } else { rval = RvalCopy(opts.cp_save->rval); } if (rval.type == RVAL_TYPE_LIST) { Rlist *rval_list = RvalRlistValue(rval); RlistFlatten(ctx, &rval_list); rval.item = rval_list; } BufferDestroy(&conv); } if (Epimenides(ctx, PromiseGetBundle(pp)->name, pp->promiser, rval, 0)) { Log(LOG_LEVEL_ERR, "Variable \"%s\" contains itself indirectly - an unkeepable promise", pp->promiser); exit(1); } else { /* See if the variable needs recursively expanding again */ Rval returnval = EvaluateFinalRval(ctx, BufferData(qualified_scope), rval, true, pp); RvalDestroy(rval); // freed before function exit rval = returnval; } if (existing_var_type != DATA_TYPE_NONE) { if (opts.ok_redefine) /* only on second iteration, else we ignore broken promises */ { ScopeDeleteVariable(BufferData(qualified_scope), pp->promiser); } else if ((THIS_AGENT_TYPE == AGENT_TYPE_COMMON) && (CompareRval(existing_var_rval, rval) == false)) { switch (rval.type) { case RVAL_TYPE_SCALAR: Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant scalar \"%s\" (was %s now %s)", pp->promiser, RvalScalarValue(existing_var_rval), RvalScalarValue(rval)); PromiseRef(LOG_LEVEL_VERBOSE, pp); break; case RVAL_TYPE_LIST: { Log(LOG_LEVEL_VERBOSE, "Redefinition of a constant list \"%s\".", pp->promiser); Writer *w = StringWriter(); RlistWrite(w, existing_var_rval.item); char *oldstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, "Old value: %s", oldstr); free(oldstr); w = StringWriter(); RlistWrite(w, rval.item); char *newstr = StringWriterClose(w); Log(LOG_LEVEL_VERBOSE, " New value: %s", newstr); free(newstr); PromiseRef(LOG_LEVEL_VERBOSE, pp); } break; default: break; } } } if (IsCf3VarString(pp->promiser)) { // Unexpanded variables, we don't do anything with RvalDestroy(rval); free(scope); BufferDestroy(&qualified_scope); return; } if (!FullTextMatch("[a-zA-Z0-9_\200-\377.]+(\\[.+\\])*", pp->promiser)) { Log(LOG_LEVEL_ERR, "Variable identifier contains illegal characters"); PromiseRef(LOG_LEVEL_ERR, pp); RvalDestroy(rval); free(scope); BufferDestroy(&qualified_scope); return; } if (opts.drop_undefined && rval.type == RVAL_TYPE_LIST) { for (Rlist *rp = rval.item; rp != NULL; rp = rp->next) { if (IsNakedVar(rp->item, '@')) { free(rp->item); rp->item = xstrdup(CF_NULL_VALUE); } } } if (!EvalContextVariablePut(ctx, (VarRef) { NULL, BufferData(qualified_scope), pp->promiser }, rval, DataTypeFromString(opts.cp_save->lval))) { Log(LOG_LEVEL_VERBOSE, "Unable to converge %s.%s value (possibly empty or infinite regression)", BufferData(qualified_scope), pp->promiser); PromiseRef(LOG_LEVEL_VERBOSE, pp); promise_result = PROMISE_RESULT_FAIL; } else { promise_result = PROMISE_RESULT_CHANGE; } } else { Log(LOG_LEVEL_ERR, "Variable %s has no promised value", pp->promiser); Log(LOG_LEVEL_ERR, "Rule from %s at/before line %zu", PromiseGetBundle(pp)->source_path, opts.cp_save->offset.line); promise_result = PROMISE_RESULT_FAIL; } /* * FIXME: Variable promise are exempt from normal evaluation logic still, so * they are not pushed to evaluation stack before being evaluated. Due to * this reason, we cannot call cfPS here to set classes, as it will error * out with ProgrammingError. * * In order to support 'classes' body for variables as well, we call * ClassAuditLog explicitly. */ ClassAuditLog(ctx, pp, a, promise_result); free(scope); BufferDestroy(&qualified_scope); RvalDestroy(rval); }
/** * Receive a transaction packet of at most CF_BUFSIZE-1 bytes, and * NULL-terminate it. * * @return 0 in case of socket closed, -1 in case of other error, or * >0 the number of bytes read. */ int ReceiveTransaction(const ConnectionInfo *conn_info, char *buffer, int *more) { char proto[CF_INBAND_OFFSET + 1] = { 0 }; int ret; /* Get control channel. */ switch(conn_info->protocol) { case CF_PROTOCOL_CLASSIC: ret = RecvSocketStream(conn_info->sd, proto, CF_INBAND_OFFSET); break; case CF_PROTOCOL_TLS: ret = TLSRecv(conn_info->ssl, proto, CF_INBAND_OFFSET); break; default: UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", conn_info->protocol); ret = -1; } if (ret == -1 || ret == 0) { return ret; } else if (ret != CF_INBAND_OFFSET) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: bogus short header (%d bytes: '%s')", ret, proto); return -1; } LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction header: ", proto, ret); char status = 'x'; int len = 0; ret = sscanf(proto, "%c %d", &status, &len); if (ret != 2) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: bogus header: %s", proto); return -1; } if (status != CF_MORE && status != CF_DONE) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: bogus header (more='%c')", status); return -1; } if (len > CF_BUFSIZE - CF_INBAND_OFFSET) { Log(LOG_LEVEL_ERR, "ReceiveTransaction: packet too long (len=%d)", len); return -1; } else if (len <= 0) { /* Zero-length packets are disallowed, because * ReceiveTransaction() == 0 currently means connection closed. */ Log(LOG_LEVEL_ERR, "ReceiveTransaction: packet too short (len=%d)", len); return -1; } if (more != NULL) { switch (status) { case CF_MORE: *more = true; break; case CF_DONE: *more = false; break; default: ProgrammingError("Unreachable, " "bogus headers have already been checked!"); } } /* Get data. */ switch(conn_info->protocol) { case CF_PROTOCOL_CLASSIC: ret = RecvSocketStream(conn_info->sd, buffer, len); break; case CF_PROTOCOL_TLS: ret = TLSRecv(conn_info->ssl, buffer, len); break; default: UnexpectedError("ReceiveTransaction: ProtocolVersion %d!", conn_info->protocol); ret = -1; } if (ret == -1 || ret == 0) { return ret; } else if (ret != len) { /* Should never happen given that we are using SSL_MODE_AUTO_RETRY and * that transaction payload < CF_BUFSIZE < TLS record size. */ Log(LOG_LEVEL_ERR, "Partial transaction read %d != %d bytes!", ret, len); return -1; } LogRaw(LOG_LEVEL_DEBUG, "ReceiveTransaction data: ", buffer, ret); return ret; }