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