1 /*
2 Copyright 2021 Northern.tech AS
3
4 This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
5
6 This program is free software; you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation; version 3.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18
19 To the extent this program is licensed as part of the Enterprise
20 versions of CFEngine, the applicable Commercial Open Source License
21 (COSL) may apply to this file if you as a licensee so wish it. See
22 included file COSL.txt.
23 */
24
25 #include <cf3.defs.h>
26
27 #include <dbm_api.h>
28 #include <files_names.h>
29 #include <files_interfaces.h>
30 #include <item_lib.h>
31 #include <vars.h>
32 #include <sort.h>
33 #include <attributes.h>
34 #include <communication.h>
35 #include <locks.h>
36 #include <logging.h>
37 #include <string_lib.h>
38 #include <misc_lib.h>
39 #include <file_lib.h>
40 #include <policy.h>
41 #include <scope.h>
42 #include <ornaments.h>
43 #include <eval_context.h>
44 #include <actuator.h>
45
46 static bool PrintFile(const char *filename, ssize_t max_lines);
47 static void ReportToFile(const char *logfile, const char *message);
48 static void ReportToLog(const char *message);
49
VerifyReportPromise(EvalContext * ctx,const Promise * pp)50 PromiseResult VerifyReportPromise(EvalContext *ctx, const Promise *pp)
51 {
52 assert(pp != NULL);
53
54 /* This check needs to happen *before* the lock is acquired otherwise the
55 * promise would be skipped in the next evaluation pass and a report with an
56 * unresolved variable reference would never be shown. */
57 if ((EvalContextGetPass(ctx) < (CF_DONEPASSES - 1)) && IsCf3VarString(pp->promiser))
58 {
59 /* Unresolved variable reference in the string to be reported and there
60 * is still a chance it will get resolved later. */
61 return PROMISE_RESULT_SKIPPED;
62 }
63
64 CfLock thislock;
65 char unique_name[CF_EXPANDSIZE];
66
67 Attributes a = GetReportsAttributes(ctx, pp);
68
69 // We let AcquireLock worry about making a unique name
70 snprintf(unique_name, CF_EXPANDSIZE - 1, "%s", pp->promiser);
71 thislock = AcquireLock(ctx, unique_name, VUQNAME, CFSTARTTIME, a.transaction.ifelapsed, a.transaction.expireafter, pp, false);
72
73 // Handle return values before locks, as we always do this
74
75 if (a.report.result)
76 {
77 // User-unwritable value last-result contains the useresult
78 if (strlen(a.report.result) > 0)
79 {
80 snprintf(unique_name, CF_BUFSIZE, "last-result[%s]", a.report.result);
81 }
82 else
83 {
84 snprintf(unique_name, CF_BUFSIZE, "last-result");
85 }
86
87 VarRef *ref = VarRefParseFromBundle(unique_name, PromiseGetBundle(pp));
88 EvalContextVariablePut(ctx, ref, pp->promiser, CF_DATA_TYPE_STRING, "source=bundle");
89 VarRefDestroy(ref);
90
91 if (thislock.lock)
92 {
93 YieldCurrentLock(thislock);
94 }
95 return PROMISE_RESULT_NOOP;
96 }
97
98 if (thislock.lock == NULL)
99 {
100 return PROMISE_RESULT_SKIPPED;
101 }
102
103 PromiseBanner(ctx, pp);
104
105 if (DONTDO || (a.transaction.action == cfa_warn))
106 {
107 cfPS(ctx, LOG_LEVEL_WARNING, PROMISE_RESULT_WARN, pp, &a, "Need to repair reports promise: %s", pp->promiser);
108 YieldCurrentLock(thislock);
109 return PROMISE_RESULT_WARN;
110 }
111
112 if (a.report.to_file)
113 {
114 ReportToFile(a.report.to_file, pp->promiser);
115 }
116 else
117 {
118 ReportToLog(pp->promiser);
119 }
120
121 PromiseResult result = PROMISE_RESULT_NOOP;
122 if (a.report.haveprintfile)
123 {
124 if (!PrintFile(a.report.filename, a.report.numlines))
125 {
126 result = PromiseResultUpdate(result, PROMISE_RESULT_FAIL);
127 }
128 }
129
130 YieldCurrentLock(thislock);
131
132 ClassAuditLog(ctx, pp, &a, result);
133 return result;
134 }
135
ReportToLog(const char * message)136 static void ReportToLog(const char *message)
137 {
138 char *report_message;
139 xasprintf(&report_message, "R: %s", message);
140
141 fputs(report_message, stdout);
142 fputc('\n', stdout);
143 LogToSystemLog(report_message, LOG_LEVEL_NOTICE);
144
145 free(report_message);
146 }
147
ReportToFile(const char * logfile,const char * message)148 static void ReportToFile(const char *logfile, const char *message)
149 {
150 FILE *fp = safe_fopen_create_perms(logfile, "a", CF_PERMS_DEFAULT);
151 if (!fp)
152 {
153 Log(LOG_LEVEL_ERR, "Could not open log file '%s', message '%s'. (fopen: %s)", logfile, message, GetErrorStr());
154 }
155 else
156 {
157 fprintf(fp, "%s\n", message);
158 fclose(fp);
159 }
160 }
161
PrintFile(const char * filename,ssize_t max_lines)162 static bool PrintFile(const char *filename, ssize_t max_lines)
163 {
164 if (!filename)
165 {
166 Log(LOG_LEVEL_VERBOSE,
167 "Printfile promise was incomplete, with no filename.");
168 return false;
169 }
170
171 FILE *fp = safe_fopen(filename, "r");
172 if (!fp)
173 {
174 Log(LOG_LEVEL_ERR,
175 "Printing of file '%s' was not possible. (fopen: %s)",
176 filename, GetErrorStr());
177 return false;
178 }
179
180 size_t line_size = CF_BUFSIZE;
181 char *line = xmalloc(line_size);
182
183 ssize_t skip_lines = 0;
184 if (max_lines < 0)
185 {
186 skip_lines = max_lines;
187 max_lines = ABS(max_lines);
188
189 while (CfReadLine(&line, &line_size, fp) != -1)
190 {
191 skip_lines++;
192 }
193 if (ferror(fp))
194 {
195 Log(LOG_LEVEL_ERR,
196 "Failed to read line from stream, (getline: %s)",
197 GetErrorStr());
198 free(line);
199 return false;
200 }
201 rewind(fp);
202 }
203
204 for (ssize_t i = 0; i < skip_lines + max_lines; i++)
205 {
206 if (CfReadLine(&line, &line_size, fp) == -1)
207 {
208 if (ferror(fp))
209 {
210 Log(LOG_LEVEL_ERR,
211 "Failed to read line from stream, (getline: %s)",
212 GetErrorStr());
213 free(line);
214 return false;
215 }
216 else
217 {
218 break;
219 }
220 }
221 if (i >= skip_lines)
222 {
223 ReportToLog(line);
224 }
225 }
226
227 fclose(fp);
228 free(line);
229
230 return true;
231 }
232