1 /* mmaudit.c
2  * This is a message modification module supporting Linux audit format
3  * in various settings. The module tries to identify the provided
4  * message as being a Linux audit record and, if so, converts it into
5  * cee-enhanced syslog format.
6  *
7  * NOTE WELL:
8  * Right now, we do not do any trust checks. So it is possible that a
9  * malicous user emits something that looks like an audit record and
10  * tries to fool the system with that. Solving this trust issue is NOT
11  * an easy thing to do. This will be worked on, as the lumberjack effort
12  * continues. Please consider the module in its current state as a proof
13  * of concept.
14  *
15  * File begun on 2012-02-23 by RGerhards
16  *
17  * Copyright 2013-2016 Adiscon GmbH.
18  *
19  * This file is part of rsyslog.
20  *
21  * Licensed under the Apache License, Version 2.0 (the "License");
22  * you may not use this file except in compliance with the License.
23  * You may obtain a copy of the License at
24  *
25  *       http://www.apache.org/licenses/LICENSE-2.0
26  *       -or-
27  *       see COPYING.ASL20 in the source distribution
28  *
29  * Unless required by applicable law or agreed to in writing, software
30  * distributed under the License is distributed on an "AS IS" BASIS,
31  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
32  * See the License for the specific language governing permissions and
33  * limitations under the License.
34  */
35 #include "config.h"
36 #include "rsyslog.h"
37 #include <stdio.h>
38 #include <stdarg.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <assert.h>
42 #include <signal.h>
43 #include <errno.h>
44 #include <unistd.h>
45 #include <ctype.h>
46 #include <json.h>
47 #include "conf.h"
48 #include "syslogd-types.h"
49 #include "template.h"
50 #include "module-template.h"
51 #include "errmsg.h"
52 #include "cfsysline.h"
53 #include "dirty.h"
54 
55 MODULE_TYPE_OUTPUT
56 MODULE_TYPE_NOKEEP
57 
58 
59 /* static data */
60 
61 /* internal structures
62  */
63 DEF_OMOD_STATIC_DATA
64 
65 typedef struct _instanceData {
66 	int dummy; /* remove when the first real parameter is needed */
67 } instanceData;
68 
69 typedef struct wrkrInstanceData {
70 	instanceData *pData;
71 } wrkrInstanceData_t;
72 
73 
74 BEGINinitConfVars		/* (re)set config variables to default values */
75 CODESTARTinitConfVars
76 ENDinitConfVars
77 
78 
79 BEGINcreateInstance
80 CODESTARTcreateInstance
81 ENDcreateInstance
82 
83 BEGINcreateWrkrInstance
84 CODESTARTcreateWrkrInstance
85 ENDcreateWrkrInstance
86 
87 
88 BEGINisCompatibleWithFeature
89 CODESTARTisCompatibleWithFeature
90 ENDisCompatibleWithFeature
91 
92 
93 BEGINfreeInstance
94 CODESTARTfreeInstance
95 ENDfreeInstance
96 
97 BEGINfreeWrkrInstance
98 CODESTARTfreeWrkrInstance
99 ENDfreeWrkrInstance
100 
101 
102 BEGINdbgPrintInstInfo
103 CODESTARTdbgPrintInstInfo
104 	dbgprintf("mmaudit\n");
105 ENDdbgPrintInstInfo
106 
107 
108 BEGINtryResume
109 CODESTARTtryResume
110 ENDtryResume
111 
112 
113 static void
skipWhitespace(uchar ** buf)114 skipWhitespace(uchar **buf)
115 {
116 	while(**buf && isspace(**buf))
117 		++(*buf);
118 }
119 
120 
121 static rsRetVal
parseName(uchar ** buf,char * name,unsigned lenName)122 parseName(uchar **buf, char *name, unsigned lenName)
123 {
124 	unsigned i;
125 	skipWhitespace(buf);
126 	--lenName; /* reserve space for '\0' */
127 	i = 0;
128 	while(**buf && **buf != '=' && lenName) {
129 		name[i++] = **buf;
130 		++(*buf), --lenName;
131 	}
132 	name[i] = '\0';
133 	return RS_RET_OK;
134 }
135 
136 
137 static rsRetVal
parseValue(uchar ** buf,char * val,unsigned lenval)138 parseValue(uchar **buf, char *val, unsigned lenval)
139 {
140 	char termc;
141 	unsigned i;
142 	DEFiRet;
143 
144 	--lenval; /* reserve space for '\0' */
145 	i = 0;
146 	if(**buf == '\0') {
147 		FINALIZE;
148 	} else if(**buf == '\'') {
149 		termc = '\'';
150 		++(*buf);
151 	} else if(**buf == '"') {
152 		termc = '"';
153 		++(*buf);
154 	} else {
155 		termc = ' ';
156 	}
157 
158 	while(**buf && **buf != termc && lenval) {
159 		val[i++] = **buf;
160 		++(*buf), --lenval;
161 	}
162 	val[i] = '\0';
163 
164 finalize_it:
165 	RETiRet;
166 }
167 
168 
169 /* parse the audit record and create json structure
170  */
171 static rsRetVal
audit_parse(uchar * buf,struct json_object ** jsonRoot)172 audit_parse(uchar *buf, struct json_object **jsonRoot)
173 {
174 	struct json_object *json;
175 	struct json_object *jval;
176 	char name[1024];
177 	char val[1024];
178 	DEFiRet;
179 
180 	*jsonRoot = json_object_new_object();
181 	if(*jsonRoot == NULL) {
182 		ABORT_FINALIZE(RS_RET_ERR);
183 	}
184 	json = json_object_new_object();
185 	json_object_object_add(*jsonRoot, "data", json);
186 
187 	while(*buf) {
188 		CHKiRet(parseName(&buf, name, sizeof(name)));
189 		if(*buf != '=') {
190 			ABORT_FINALIZE(RS_RET_ERR);
191 		}
192 		++buf;
193 		CHKiRet(parseValue(&buf, val, sizeof(val)));
194 		jval = json_object_new_string(val);
195 		json_object_object_add(json, name, jval);
196 	}
197 
198 
199 finalize_it:
200 	RETiRet;
201 }
202 
203 
204 BEGINdoAction_NoStrings
205 	smsg_t **ppMsg = (smsg_t **) pMsgData;
206 	smsg_t *pMsg = ppMsg[0];
207 	uchar *buf;
208 	int typeID;
209 	struct json_object *jsonRoot;
210 	struct json_object *json;
211 	struct json_object *jval;
212 	int i;
213 	char auditID[1024];
214 	int bSuccess = 0;
215 CODESTARTdoAction
216 	/* note that we can performance-optimize the interface, but this also
217 	 * requires changes to the libraries. For now, we accept message
218 	 * duplication. -- rgerhards, 2010-12-01
219 	 */
220 	buf = getMSG(pMsg);
221 
222 	while(*buf && isspace(*buf)) {
223 		++buf;
224 	}
225 
226 	if(*buf == '\0' || strncmp((char*)buf, "type=", 5)) {
227 		DBGPRINTF("mmaudit: type= undetected: '%s'\n", buf);
228 		FINALIZE;
229 	}
230 	buf += 5;
231 
232 	typeID = 0;
233 	while(*buf && isdigit(*buf)) {
234 		typeID = typeID * 10 + *buf - '0';
235 		++buf;
236 	}
237 
238 	if(*buf == '\0' || strncmp((char*)buf, " audit(", sizeof(" audit(")-1)) {
239 		DBGPRINTF("mmaudit: audit( header not found: %s'\n", buf);
240 		FINALIZE;
241 	}
242 	buf += sizeof(" audit(");
243 
244 	for(i = 0 ; i < (int) (sizeof(auditID)-2) && *buf && *buf != ')' ; ++i) {
245 		auditID[i] = *buf++;
246 	}
247 	auditID[i] = '\0';
248 	if(*buf != ')' || *(buf+1) != ':') {
249 		DBGPRINTF("mmaudit: trailer '):' not found, no audit record: %s'\n", buf);
250 		FINALIZE;
251 	}
252 	buf += 2;
253 
254 	audit_parse(buf, &jsonRoot);
255 	if(jsonRoot == NULL) {
256 		DBGPRINTF("mmaudit: audit parse error, assuming no "
257 			  "audit message: '%s'\n", buf);
258 		FINALIZE;
259 	}
260 
261 	/* we now need to shuffle the "outer" properties into that stream */
262 	json = json_object_new_object();
263 	json_object_object_add(jsonRoot, "hdr", json);
264 	jval = json_object_new_string(auditID);
265 	json_object_object_add(json, "auditid", jval);
266 	jval = json_object_new_int(typeID);
267 	json_object_object_add(json, "type", jval);
268 
269 	msgAddJSON(pMsg, (uchar*)"!audit", jsonRoot, 0, 0);
270 	bSuccess = 1;
271 
272 finalize_it:
273 	MsgSetParseSuccess(pMsg, bSuccess);
274 ENDdoAction
275 
276 
277 BEGINparseSelectorAct
278 CODESTARTparseSelectorAct
279 CODE_STD_STRING_REQUESTparseSelectorAct(1)
280 	/* first check if this config line is actually for us */
281 	if(strncmp((char*) p, ":mmaudit:", sizeof(":mmaudit:") - 1)) {
282 		ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
283 	}
284 
285 	/* ok, if we reach this point, we have something for us */
286 	p += sizeof(":mmaudit:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
287 	CHKiRet(createInstance(&pData));
288 
289 	/* check if a non-standard template is to be applied */
290 	if(*(p-1) == ';')
291 		--p;
292 	/* we call the function below because we need to call it via our interface definition. However,
293 	 * the format specified (if any) is always ignored.
294 	 */
295 	iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat");
296 CODE_STD_FINALIZERparseSelectorAct
297 ENDparseSelectorAct
298 
299 
300 BEGINmodExit
301 CODESTARTmodExit
302 ENDmodExit
303 
304 
305 BEGINqueryEtryPt
306 CODESTARTqueryEtryPt
307 CODEqueryEtryPt_STD_OMOD_QUERIES
308 CODEqueryEtryPt_STD_OMOD8_QUERIES
309 ENDqueryEtryPt
310 
311 
312 BEGINmodInit()
313 	rsRetVal localRet;
314 	rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts);
315 	unsigned long opts;
316 	int bMsgPassingSupported;
317 CODESTARTmodInit
318 INITLegCnfVars
319 	*ipIFVersProvided = CURR_MOD_IF_VERSION;
320 		/* we only support the current interface specification */
321 CODEmodInit_QueryRegCFSLineHdlr
322 	/* check if the rsyslog core supports parameter passing code */
323 	bMsgPassingSupported = 0;
324 	localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts",
325 			&pomsrGetSupportedTplOpts);
326 	if(localRet == RS_RET_OK) {
327 		/* found entry point, so let's see if core supports msg passing */
328 		CHKiRet((*pomsrGetSupportedTplOpts)(&opts));
329 		if(opts & OMSR_TPL_AS_MSG)
330 			bMsgPassingSupported = 1;
331 	} else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) {
332 		ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */
333 	}
334 
335 	if(!bMsgPassingSupported) {
336 		DBGPRINTF("mmaudit: msg-passing is not supported by rsyslog core, "
337 			  "can not continue.\n");
338 		ABORT_FINALIZE(RS_RET_NO_MSG_PASSING);
339 	}
340 
341 ENDmodInit
342 
343 /* vi:set ai:
344  */
345