1 /* omusrmsg.c
2  * This is the implementation of the build-in output module for sending
3  * user messages.
4  *
5  * NOTE: read comments in module-template.h to understand how this file
6  *       works!
7  *
8  * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c, which at the
9  * time of the fork from sysklogd was under BSD license)
10  *
11  * Copyright 2007-2018 Adiscon GmbH.
12  *
13  * This file is part of rsyslog.
14  *
15  * Licensed under the Apache License, Version 2.0 (the "License");
16  * you may not use this file except in compliance with the License.
17  * You may obtain a copy of the License at
18  *
19  *       http://www.apache.org/licenses/LICENSE-2.0
20  *       -or-
21  *       see COPYING.ASL20 in the source distribution
22  *
23  * Unless required by applicable law or agreed to in writing, software
24  * distributed under the License is distributed on an "AS IS" BASIS,
25  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26  * See the License for the specific language governing permissions and
27  * limitations under the License.
28  */
29 #include "config.h"
30 #include <stdio.h>
31 #include <stdarg.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <assert.h>
35 #include <signal.h>
36 #include <ctype.h>
37 #include <sys/param.h>
38 #ifdef HAVE_UTMP_H
39 #  include <utmp.h>
40 #  define STRUCTUTMP struct utmp
41 #  define UTNAME ut_name
42 #else
43 #  include <utmpx.h>
44 #  define STRUCTUTMP struct utmpx
45 #  define UTNAME ut_user
46 #endif
47 #include <unistd.h>
48 #include <sys/uio.h>
49 #include <sys/stat.h>
50 #include <errno.h>
51 #if HAVE_FCNTL_H
52 #include <fcntl.h>
53 #else
54 #include <sys/msgbuf.h>
55 #endif
56 #ifdef HAVE_PATHS_H
57 #include <paths.h>
58 #endif
59 #include "rsyslog.h"
60 #include "srUtils.h"
61 #include "stringbuf.h"
62 #include "syslogd-types.h"
63 #include "conf.h"
64 #include "omusrmsg.h"
65 #include "module-template.h"
66 #include "errmsg.h"
67 
68 
69 /* portability: */
70 #ifndef _PATH_DEV
71 #	define _PATH_DEV	"/dev/"
72 #endif
73 
74 #ifdef UT_NAMESIZE
75 # define UNAMESZ	UT_NAMESIZE	/* length of a login name */
76 #else
77 # define UNAMESZ	32	/* length of a login name, 32 seems current (2018) good bet */
78 #endif
79 #define MAXUNAMES	20	/* maximum number of user names */
80 
81 #ifdef OS_SOLARIS
82 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
83 #endif
84 
85 MODULE_TYPE_OUTPUT
86 MODULE_TYPE_NOKEEP
87 MODULE_CNFNAME("omusrmsg")
88 
89 /* internal structures
90  */
91 DEF_OMOD_STATIC_DATA
92 
93 typedef struct _instanceData {
94 	int bIsWall; /* 1- is wall, 0 - individual users */
95 	char uname[MAXUNAMES][UNAMESZ+1];
96 	uchar *tplName;
97 } instanceData;
98 
99 typedef struct wrkrInstanceData {
100 	instanceData *pData;
101 } wrkrInstanceData_t;
102 
103 typedef struct configSettings_s {
104 	EMPTY_STRUCT
105 } configSettings_t;
106 static configSettings_t __attribute__((unused)) cs;
107 
108 
109 /* tables for interfacing with the v6 config system */
110 /* action (instance) parameters */
111 static struct cnfparamdescr actpdescr[] = {
112 	{ "users", eCmdHdlrString, CNFPARAM_REQUIRED },
113 	{ "template", eCmdHdlrGetWord, 0 }
114 };
115 static struct cnfparamblk actpblk =
116 	{ CNFPARAMBLK_VERSION,
117 	  sizeof(actpdescr)/sizeof(struct cnfparamdescr),
118 	  actpdescr
119 	};
120 
121 BEGINinitConfVars		/* (re)set config variables to default values */
122 CODESTARTinitConfVars
123 ENDinitConfVars
124 
125 
126 BEGINcreateInstance
127 CODESTARTcreateInstance
128 ENDcreateInstance
129 
130 
131 BEGINcreateWrkrInstance
132 CODESTARTcreateWrkrInstance
133 ENDcreateWrkrInstance
134 
135 
136 BEGINisCompatibleWithFeature
137 CODESTARTisCompatibleWithFeature
138 	if(eFeat == sFEATURERepeatedMsgReduction)
139 		iRet = RS_RET_OK;
140 ENDisCompatibleWithFeature
141 
142 
143 BEGINfreeInstance
144 CODESTARTfreeInstance
145 	free(pData->tplName);
146 ENDfreeInstance
147 
148 
149 BEGINfreeWrkrInstance
150 CODESTARTfreeWrkrInstance
151 ENDfreeWrkrInstance
152 
153 
154 BEGINdbgPrintInstInfo
155 	register int i;
156 CODESTARTdbgPrintInstInfo
157 	for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++)
158 		dbgprintf("%s, ", pData->uname[i]);
159 ENDdbgPrintInstInfo
160 
161 
162 /**
163  * BSD setutent/getutent() replacement routines
164  * The following routines emulate setutent() and getutent() under
165  * BSD because they are not available there. We only emulate what we actually
166  * need! rgerhards 2005-03-18
167  */
168 #ifdef OS_BSD
169 /* Since version 900007, FreeBSD has a POSIX compliant <utmpx.h> */
170 #if defined(__DragonFly__) || defined(__FreeBSD__)
171 #  define setutent(void) setutxent(void)
172 #  define getutent(void) getutxent(void)
173 #  define endutent(void) endutxent(void)
174 #else
175 static FILE *BSD_uf = NULL;
176 void setutent(void)
177 {
178 	assert(BSD_uf == NULL);
179 	if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) {
180 		LogError(errno, NO_ERRCODE, "error opening utmp %s", _PATH_UTMP);
181 		return;
182 	}
183 }
184 
185 STRUCTUTMP* getutent(void)
186 {
187 	static STRUCTUTMP st_utmp;
188 
189 	if(fread((char *)&st_utmp, sizeof(st_utmp), 1, BSD_uf) != 1)
190 		return NULL;
191 
192 	return(&st_utmp);
193 }
194 
195 void endutent(void)
196 {
197 	fclose(BSD_uf);
198 	BSD_uf = NULL;
199 }
200 #endif /* if defined(__FreeBSD__) */
201 #endif  /* #ifdef OS_BSD */
202 
203 
204 /*  WALLMSG -- Write a message to the world at large
205  *
206  *	Write the specified message to either the entire
207  *	world, or a list of approved users.
208  *
209  * rgerhards, 2005-10-19: applying the following sysklogd patch:
210  * Tue May  4 16:52:01 CEST 2004: Solar Designer <solar@openwall.com>
211  *	Adjust the size of a variable to prevent a buffer overflow
212  *	should _PATH_DEV ever contain something different than "/dev/".
213  * rgerhards, 2008-07-04: changing the function to no longer use fork() but
214  * 	continue run on its thread instead.
215  */
wallmsg(uchar * pMsg,instanceData * pData)216 static rsRetVal wallmsg(uchar* pMsg, instanceData *pData)
217 {
218 
219 	uchar szErr[512];
220 	char p[sizeof(_PATH_DEV) + UNAMESZ];
221 	register int i;
222 	int errnoSave;
223 	int ttyf;
224 	int wrRet;
225 	STRUCTUTMP ut;
226 	STRUCTUTMP *uptr;
227 	struct stat statb;
228 	DEFiRet;
229 
230 	assert(pMsg != NULL);
231 
232 	/* open the user login file */
233 	setutent();
234 
235 	/* scan the user login file */
236 	while((uptr = getutent())) {
237 		memcpy(&ut, uptr, sizeof(ut));
238 		/* is this slot used? */
239 		if(ut.UTNAME[0] == '\0')
240 			continue;
241 #ifndef OS_BSD
242 		if(ut.ut_type != USER_PROCESS)
243 			continue;
244 #endif
245 		if(!(memcmp (ut.UTNAME,"LOGIN", 6))) /* paranoia */
246 			continue;
247 
248 		/* should we send the message to this user? */
249 		if(pData->bIsWall == 0) {
250 			for(i = 0; i < MAXUNAMES; i++) {
251 				if(!pData->uname[i][0]) {
252 					i = MAXUNAMES;
253 					break;
254 				}
255 				if(strncmp(pData->uname[i], ut.UTNAME, UNAMESZ) == 0)
256 					break;
257 			}
258 			if(i == MAXUNAMES) /* user not found? */
259 				continue; /* on to next user! */
260 		}
261 
262 		/* compute the device name */
263 		strcpy(p, _PATH_DEV);
264 		strncat(p, ut.ut_line, UNAMESZ);
265 
266 		/* we must be careful when writing to the terminal. A terminal may block
267 		 * (for example, a user has pressed <ctl>-s). In that case, we can not
268 		 * wait indefinitely. So we need to use non-blocking I/O. In case we would
269 		 * block, we simply do not send the message, because that's the best we can
270 		 * do. -- rgerhards, 2008-07-04
271 		 */
272 
273 		/* open the terminal */
274 		if((ttyf = open(p, O_WRONLY|O_NOCTTY|O_NONBLOCK)) >= 0) {
275 			if(fstat(ttyf, &statb) == 0 && (statb.st_mode & S_IWRITE)) {
276 				wrRet = write(ttyf, pMsg, strlen((char*)pMsg));
277 				if(Debug && wrRet == -1) {
278 					/* we record the state to the debug log */
279 					errnoSave = errno;
280 					rs_strerror_r(errno, (char*)szErr, sizeof(szErr));
281 					dbgprintf("write to terminal '%s' failed with [%d]:%s\n",
282 						  p, errnoSave, szErr);
283 				}
284 			}
285 			close(ttyf);
286 		}
287 	}
288 
289 	/* close the user login file */
290 	endutent();
291 	RETiRet;
292 }
293 
294 
295 BEGINtryResume
296 CODESTARTtryResume
297 ENDtryResume
298 
299 BEGINdoAction
300 CODESTARTdoAction
301 	dbgprintf("\n");
302 	iRet = wallmsg(ppString[0], pWrkrData->pData);
303 ENDdoAction
304 
305 
306 static void
populateUsers(instanceData * pData,es_str_t * usrs)307 populateUsers(instanceData *pData, es_str_t *usrs)
308 {
309 	int i;
310 	int iDst;
311 	es_size_t iUsr;
312 	es_size_t len;
313 	uchar *c;
314 
315 	len = es_strlen(usrs);
316 	c = es_getBufAddr(usrs);
317 	pData->bIsWall = 0; /* write to individual users */
318 	iUsr = 0;
319 	for(i = 0 ; i < MAXUNAMES && iUsr < len ; ++i) {
320 		for(  iDst = 0
321 		    ; iDst < UNAMESZ && iUsr < len && c[iUsr] != ','
322 		    ; ++iDst, ++iUsr) {
323 			pData->uname[i][iDst] = c[iUsr];
324 		}
325 		pData->uname[i][iDst] = '\0';
326 		DBGPRINTF("omusrmsg: send to user '%s'\n", pData->uname[i]);
327 		if(iUsr < len && c[iUsr] != ',') {
328 			LogError(0, RS_RET_ERR, "user name '%s...' too long - "
329 				"ignored", pData->uname[i]);
330 			--i;
331 			++iUsr;
332 			while(iUsr < len && c[iUsr] != ',')
333 				++iUsr; /* skip to next name */
334 		} else if(iDst == 0) {
335 			LogError(0, RS_RET_ERR, "no user name given - "
336 				"ignored");
337 			--i;
338 			++iUsr;
339 			while(iUsr < len && c[iUsr] != ',')
340 				++iUsr; /* skip to next name */
341 		}
342 		if(iUsr < len) {
343 			++iUsr; /* skip "," */
344 			while(iUsr < len && isspace(c[iUsr]))
345 				++iUsr; /* skip whitespace */
346 		}
347 	}
348 	if(i == MAXUNAMES && iUsr != len) {
349 		LogError(0, RS_RET_ERR, "omusrmsg supports only up to %d "
350 			"user names in a single action - all others have been ignored",
351 			MAXUNAMES);
352 	}
353 }
354 
355 
356 static inline void
setInstParamDefaults(instanceData * pData)357 setInstParamDefaults(instanceData *pData)
358 {
359 	pData->bIsWall = 0;
360 	pData->tplName = NULL;
361 }
362 
363 BEGINnewActInst
364 	struct cnfparamvals *pvals;
365 	int i;
366 CODESTARTnewActInst
367 	if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
368 		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
369 	}
370 
371 	CHKiRet(createInstance(&pData));
372 	setInstParamDefaults(pData);
373 
374 	CODE_STD_STRING_REQUESTnewActInst(1)
375 	for(i = 0 ; i < actpblk.nParams ; ++i) {
376 		if(!pvals[i].bUsed)
377 			continue;
378 		if(!strcmp(actpblk.descr[i].name, "users")) {
379 			if(!es_strbufcmp(pvals[i].val.d.estr, (uchar*)"*", 1)) {
380 				pData->bIsWall = 1;
381 			} else {
382 				populateUsers(pData, pvals[i].val.d.estr);
383 			}
384 		} else if(!strcmp(actpblk.descr[i].name, "template")) {
385 			pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
386 		} else {
387 			dbgprintf("omusrmsg: program error, non-handled "
388 			  "param '%s'\n", actpblk.descr[i].name);
389 		}
390 	}
391 
392 	if(pData->tplName == NULL) {
393 		CHKiRet(OMSRsetEntry(*ppOMSR, 0,
394 			(uchar*) strdup(pData->bIsWall ? " WallFmt" : " StdUsrMsgFmt"),
395 			OMSR_NO_RQD_TPL_OPTS));
396 	} else {
397 		CHKiRet(OMSRsetEntry(*ppOMSR, 0,
398 			(uchar*) strdup((char*) pData->tplName),
399 			OMSR_NO_RQD_TPL_OPTS));
400 	}
401 CODE_STD_FINALIZERnewActInst
402 	cnfparamvalsDestruct(pvals, &actpblk);
403 ENDnewActInst
404 
405 
406 BEGINparseSelectorAct
407 	es_str_t *usrs;
408 	int bHadWarning;
409 CODESTARTparseSelectorAct
410 CODE_STD_STRING_REQUESTparseSelectorAct(1)
411 	bHadWarning = 0;
412 	if(!strncmp((char*) p, ":omusrmsg:", sizeof(":omusrmsg:") - 1)) {
413 		p += sizeof(":omusrmsg:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
414 	} else {
415 		if(!*p || !((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
416 	   || (*p >= '0' && *p <= '9') || *p == '_' || *p == '.' || *p == '*')) {
417 			ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
418 		} else {
419 			LogMsg(0, RS_RET_OUTDATED_STMT, LOG_WARNING,
420 				"action '%s' treated as ':omusrmsg:%s' - please "
421 				"use ':omusrmsg:%s' syntax instead, '%s' will "
422 				"not be supported in the future",
423 				p, p, p, p);
424 			bHadWarning = 1;
425 		}
426 	}
427 
428 	CHKiRet(createInstance(&pData));
429 
430 	if(*p == '*') { /* wall */
431 		dbgprintf("write-all");
432 		++p; /* eat '*' */
433 		pData->bIsWall = 1; /* write to all users */
434 		CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " WallFmt"));
435 	} else {
436 		/* everything else is currently treated as a user name */
437 		usrs = es_newStr(128);
438 		while(*p && *p != ';') {
439 			es_addChar(&usrs, *p);
440 			++p;
441 		}
442 		populateUsers(pData, usrs);
443 		es_deleteStr(usrs);
444 		if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*)" StdUsrMsgFmt"))
445 			!= RS_RET_OK)
446 			goto finalize_it;
447 	}
448 	if(iRet == RS_RET_OK && bHadWarning)
449 		iRet = RS_RET_OK_WARN;
450 CODE_STD_FINALIZERparseSelectorAct
451 ENDparseSelectorAct
452 
453 
454 BEGINmodExit
455 CODESTARTmodExit
456 ENDmodExit
457 
458 
459 BEGINqueryEtryPt
460 CODESTARTqueryEtryPt
461 CODEqueryEtryPt_STD_OMOD_QUERIES
462 CODEqueryEtryPt_STD_OMOD8_QUERIES
463 CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES
464 ENDqueryEtryPt
465 
466 
467 BEGINmodInit(UsrMsg)
468 CODESTARTmodInit
469 INITLegCnfVars
470 	*ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
471 CODEmodInit_QueryRegCFSLineHdlr
472 ENDmodInit
473 
474 /* vim:set ai:
475  */
476