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