1 /*
2  * atheme-services: A collection of minimalist IRC services
3  * logger.c: Logging routines
4  *
5  * Copyright (c) 2005-2009 Atheme Project (http://www.atheme.org)
6  *
7  * Permission to use, copy, modify, and/or distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
12  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
13  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
14  * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
15  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
16  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
17  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
18  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
19  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
20  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
21  * POSSIBILITY OF SUCH DAMAGE.
22  */
23 
24 #include "atheme.h"
25 
26 static logfile_t *log_file;
27 int log_force;
28 
29 static mowgli_list_t log_files = { NULL, NULL, 0 };
30 
31 /* private destructor function for logfile_t. */
logfile_delete_file(void * vdata)32 static void logfile_delete_file(void *vdata)
33 {
34 	logfile_t *lf = (logfile_t *) vdata;
35 
36 	logfile_unregister(lf);
37 
38 	fclose(lf->log_file);
39 	free(lf->log_path);
40 	metadata_delete_all(lf);
41 	free(lf);
42 }
43 
logfile_delete_channel(void * vdata)44 static void logfile_delete_channel(void *vdata)
45 {
46 	logfile_t *lf = (logfile_t *) vdata;
47 
48 	logfile_unregister(lf);
49 
50 	free(lf->log_path);
51 	metadata_delete_all(lf);
52 	free(lf);
53 }
54 
logfile_join_channels(channel_t * c)55 static void logfile_join_channels(channel_t *c)
56 {
57 	mowgli_node_t *n;
58 
59 	return_if_fail(c != NULL);
60 
61 	MOWGLI_ITER_FOREACH(n, log_files.head)
62 	{
63 		logfile_t *lf = n->data;
64 
65 		if (!irccasecmp(c->name, lf->log_path))
66 		{
67 			if (!(c->flags & CHAN_LOG))
68 			{
69 				c->flags |= CHAN_LOG;
70 				joinall(lf->log_path);
71 			}
72 			return;
73 		}
74 	}
75 }
76 
logfile_join_service(service_t * svs)77 static void logfile_join_service(service_t *svs)
78 {
79 	mowgli_node_t *n;
80 	channel_t *c;
81 
82 	return_if_fail(svs != NULL && svs->me != NULL);
83 
84 	/* no botserv bots */
85 	if (svs->botonly)
86 		return;
87 
88 	MOWGLI_ITER_FOREACH(n, log_files.head)
89 	{
90 		logfile_t *lf = n->data;
91 
92 		if (!VALID_GLOBAL_CHANNEL_PFX(lf->log_path))
93 			continue;
94 		c = channel_find(lf->log_path);
95 		if (c == NULL || !(c->flags & CHAN_LOG))
96 			continue;
97 		join(c->name, svs->me->nick);
98 	}
99 }
100 
logfile_part_removed(void * unused)101 static void logfile_part_removed(void *unused)
102 {
103 	channel_t *c;
104 	mowgli_patricia_iteration_state_t state;
105 	mowgli_node_t *n;
106 	bool valid;
107 
108 	MOWGLI_PATRICIA_FOREACH(c, &state, chanlist)
109 	{
110 		if (!(c->flags & CHAN_LOG))
111 			continue;
112 		valid = false;
113 		MOWGLI_ITER_FOREACH(n, log_files.head)
114 		{
115 			logfile_t *lf = n->data;
116 
117 			if (!irccasecmp(c->name, lf->log_path))
118 			{
119 				valid = true;
120 				break;
121 			}
122 		}
123 		if (!valid)
124 		{
125 			c->flags &= ~CHAN_LOG;
126 			partall(c->name);
127 		}
128 	}
129 }
130 
131 /*
132  * logfile_strip_control_codes(const char *buf)
133  *
134  * Duplicates a string, stripping control codes in the
135  * process.
136  *
137  * Inputs:
138  *       - buffer to strip
139  *
140  * Outputs:
141  *       - duplicated buffer without control codes
142  *
143  * Side Effects:
144  *       - none
145  */
146 static const char *
logfile_strip_control_codes(const char * buf)147 logfile_strip_control_codes(const char *buf)
148 {
149 	static char outbuf[BUFSIZE];
150 	const char *in = buf;
151 	char *out = outbuf;
152 
153 	for (; *in != '\0'; in++)
154 	{
155 		if (*in > 31)
156 		{
157 			*out++ = *in;
158 			continue;
159 		}
160 		else if (*in == 3)
161 		{
162 			in++;
163 			while (isdigit((unsigned char)*in))
164 				in++;
165 		}
166 	}
167 
168 	*out = '\0';
169 	return outbuf;
170 }
171 
172 /*
173  * logfile_write(logfile_t *lf, const char *buf)
174  *
175  * Writes an I/O stream to a static file.
176  *
177  * Inputs:
178  *       - logfile_t representing the I/O stream.
179  *       - data to write to the file
180  *
181  * Outputs:
182  *       - none
183  *
184  * Side Effects:
185  *       - none
186  */
logfile_write(logfile_t * lf,const char * buf)187 static void logfile_write(logfile_t *lf, const char *buf)
188 {
189 	char datetime[BUFSIZE];
190 	time_t t;
191 	struct tm tm;
192 
193 	return_if_fail(lf != NULL);
194 	return_if_fail(lf->log_file != NULL);
195 	return_if_fail(buf != NULL);
196 
197 	time(&t);
198 	tm = *localtime(&t);
199 	strftime(datetime, sizeof datetime, "[%Y-%m-%d %H:%M:%S]", &tm);
200 
201 	fprintf((FILE *) lf->log_file, "%s %s\n", datetime, logfile_strip_control_codes(buf));
202 	fflush((FILE *) lf->log_file);
203 }
204 
205 /*
206  * logfile_write_irc(logfile_t *lf, const char *buf)
207  *
208  * Writes an I/O stream to an IRC target.
209  *
210  * Inputs:
211  *       - logfile_t representing the I/O stream.
212  *       - data to write to the IRC target
213  *
214  * Outputs:
215  *       - none
216  *
217  * Side Effects:
218  *       - none
219  */
logfile_write_irc(logfile_t * lf,const char * buf)220 static void logfile_write_irc(logfile_t *lf, const char *buf)
221 {
222 	channel_t *c;
223 
224 	return_if_fail(lf != NULL);
225 	return_if_fail(lf->log_path != NULL);
226 	return_if_fail(buf != NULL);
227 
228 	if (!me.connected || me.bursting)
229 		return;
230 
231 	c = channel_find(lf->log_path);
232 	if (c != NULL && c->flags & CHAN_LOG)
233 	{
234 		size_t targetlen;
235 		char targetbuf[NICKLEN];
236 		service_t *svs = NULL;
237 
238 		memset(targetbuf, '\0', sizeof targetbuf);
239 		targetlen = (strchr(buf, ' ') - buf);
240 
241 		if (targetlen < NICKLEN)
242 		{
243 			mowgli_strlcpy(targetbuf, buf, sizeof targetbuf);
244 			targetbuf[targetlen] = '\0';
245 
246 			svs = service_find_nick(targetbuf);
247 			targetlen++;
248 
249 			if (svs == NULL)
250 				targetlen = 0;
251 		}
252 		else
253 			targetlen = 0;
254 
255 		if (svs == NULL)
256 			svs = service_find("operserv");
257 
258 		if (svs == NULL)
259 			svs = service_find_any();
260 
261 		if (svs != NULL && svs->me != NULL)
262 			msg(svs->me->nick, c->name, "%s", (buf + targetlen));
263 		else
264 			wallops("%s", (buf + targetlen));
265 	}
266 }
267 
268 /*
269  * logfile_write_snotices(logfile_t *lf, const char *buf)
270  *
271  * Writes an I/O stream to IRC snotes.
272  *
273  * Inputs:
274  *       - logfile_t representing the I/O stream.
275  *       - data to write to the IRC target
276  *
277  * Outputs:
278  *       - none
279  *
280  * Side Effects:
281  *       - none
282  */
logfile_write_snotices(logfile_t * lf,const char * buf)283 static void logfile_write_snotices(logfile_t *lf, const char *buf)
284 {
285 	channel_t *c;
286 
287 	return_if_fail(lf != NULL);
288 	return_if_fail(lf->log_path != NULL);
289 	return_if_fail(buf != NULL);
290 
291 	if (!me.connected || me.bursting)
292 		return;
293 
294 	wallops("%s", buf);
295 }
296 
297 /*
298  * logfile_register(logfile_t *lf)
299  *
300  * Registers a log I/O stream.
301  *
302  * Inputs:
303  *       - logfile_t representing the I/O stream.
304  *
305  * Outputs:
306  *       - none
307  *
308  * Side Effects:
309  *       - log_files is populated with the given object.
310  */
logfile_register(logfile_t * lf)311 void logfile_register(logfile_t *lf)
312 {
313 	mowgli_node_add(lf, &lf->node, &log_files);
314 }
315 
316 /*
317  * logfile_unregister(logfile_t *lf)
318  *
319  * Unregisters a log I/O stream.
320  *
321  * Inputs:
322  *       - logfile_t representing the I/O stream.
323  *
324  * Outputs:
325  *       - none
326  *
327  * Side Effects:
328  *       - the given object is removed from log_files, but remains valid.
329  */
logfile_unregister(logfile_t * lf)330 void logfile_unregister(logfile_t *lf)
331 {
332 	mowgli_node_delete(&lf->node, &log_files);
333 }
334 
335 /*
336  * logfile_new(const char *log_path, unsigned int log_mask)
337  *
338  * Logfile object factory.
339  *
340  * Inputs:
341  *       - path to new logfile
342  *       - bitmask of events to log
343  *
344  * Outputs:
345  *       - a logfile_t object describing how this logfile should be used
346  *
347  * Side Effects:
348  *       - log_files is populated with the newly created logfile_t.
349  */
logfile_new(const char * path,unsigned int log_mask)350 logfile_t *logfile_new(const char *path, unsigned int log_mask)
351 {
352 	static bool hooked = false;
353 	static time_t lastfail = 0;
354 	channel_t *c;
355 	logfile_t *lf = scalloc(sizeof(logfile_t), 1);
356 
357 	if (!strcasecmp(path, "!snotices") || !strcasecmp(path, "!wallops"))
358 	{
359 		object_init(object(lf), path, logfile_delete_channel);
360 
361 		lf->log_path = sstrdup(path);
362 		lf->log_type = LOG_INTERACTIVE;
363 		lf->write_func = logfile_write_snotices;
364 	}
365 	else if (!VALID_GLOBAL_CHANNEL_PFX(path))
366 	{
367 		object_init(object(lf), path, logfile_delete_file);
368 		if ((lf->log_file = fopen(path, "a")) == NULL)
369 		{
370 			free(lf);
371 
372 			if (me.connected && lastfail + 3600 < CURRTIME)
373 			{
374 				lastfail = CURRTIME;
375 				wallops(_("Could not open log file (%s), log entries will be missing!"), strerror(errno));
376 			}
377 
378 			return NULL;
379 		}
380 #ifdef FD_CLOEXEC
381 		fcntl(fileno((FILE *)lf->log_file), F_SETFD, FD_CLOEXEC);
382 #endif
383 		lf->log_path = sstrdup(path);
384 		lf->log_type = LOG_NONINTERACTIVE;
385 		lf->write_func = logfile_write;
386 	}
387 	else
388 	{
389 		object_init(object(lf), path, logfile_delete_channel);
390 
391 		lf->log_path = sstrdup(path);
392 		lf->log_type = LOG_INTERACTIVE;
393 		lf->write_func = logfile_write_irc;
394 
395 		c = channel_find(lf->log_path);
396 
397 		if (me.connected && c != NULL)
398 		{
399 			joinall(lf->log_path);
400 			c->flags |= CHAN_LOG;
401 		}
402 		if (!hooked)
403 		{
404 			hook_add_event("channel_add");
405 			hook_add_channel_add(logfile_join_channels);
406 			hook_add_event("service_introduce");
407 			hook_add_service_introduce(logfile_join_service);
408 			hook_add_event("config_ready");
409 			hook_add_config_ready(logfile_part_removed);
410 			hooked = true;
411 		}
412 	}
413 
414 	lf->log_mask = log_mask;
415 
416 	logfile_register(lf);
417 
418 	return lf;
419 }
420 
421 /*
422  * log_open(void)
423  *
424  * Initializes the logging subsystem.
425  *
426  * Inputs:
427  *       - none
428  *
429  * Outputs:
430  *       - none
431  *
432  * Side Effects:
433  *       - the log_files list is populated with the master
434  *         atheme.log reference.
435  */
log_open(void)436 void log_open(void)
437 {
438 	log_file = logfile_new(log_path, LG_ERROR | LG_INFO | LG_CMD_ADMIN);
439 }
440 
441 /*
442  * log_shutdown(void)
443  *
444  * Shuts down the logging subsystem.
445  *
446  * Inputs:
447  *       - none
448  *
449  * Outputs:
450  *       - none
451  *
452  * Side Effects:
453  *       - logfile_t objects in the log_files list are destroyed.
454  */
log_shutdown(void)455 void log_shutdown(void)
456 {
457 	mowgli_node_t *n, *tn;
458 
459 	MOWGLI_ITER_FOREACH_SAFE(n, tn, log_files.head)
460 		object_unref(n->data);
461 }
462 
463 /*
464  * log_debug_enabled(void)
465  *
466  * Determines whether debug logging (LG_DEBUG and/or LG_RAWDATA) is enabled.
467  *
468  * Inputs:
469  *       - none
470  *
471  * Outputs:
472  *       - boolean
473  *
474  * Side Effects:
475  *       - none
476  */
log_debug_enabled(void)477 bool log_debug_enabled(void)
478 {
479 	mowgli_node_t *n;
480 	logfile_t *lf;
481 
482 	if (log_force)
483 		return true;
484 	MOWGLI_ITER_FOREACH(n, log_files.head)
485 	{
486 		lf = n->data;
487 		if (lf->log_mask & (LG_DEBUG | LG_RAWDATA))
488 			return true;
489 	}
490 	return false;
491 }
492 
493 /*
494  * log_master_set_mask(unsigned int mask)
495  *
496  * Sets the logging mask for the main log file.
497  *
498  * Inputs:
499  *       - mask
500  *
501  * Outputs:
502  *       - none
503  *
504  * Side Effects:
505  *       - logging mask updated
506  */
log_master_set_mask(unsigned int mask)507 void log_master_set_mask(unsigned int mask)
508 {
509 	/* couldn't be opened etc */
510 	if (log_file == NULL)
511 		return;
512 	log_file->log_mask = mask;
513 }
514 
515 /*
516  * logfile_find_mask(unsigned int log_mask)
517  *
518  * Returns a log file that logs all of the bits in log_mask (and possibly
519  * more), or NULL if there is none. If an exact match exists, it is returned.
520  *
521  * Inputs:
522  *       - log mask
523  *
524  * Outputs:
525  *       - logfile_t object pointer, guaranteed to be a file, not some
526  *         other kind of log stream
527  *
528  * Side Effects:
529  *       - none
530  */
logfile_find_mask(unsigned int log_mask)531 logfile_t *logfile_find_mask(unsigned int log_mask)
532 {
533 	mowgli_node_t *n;
534 	logfile_t *lf;
535 
536 	MOWGLI_ITER_FOREACH(n, log_files.head)
537 	{
538 		lf = n->data;
539 		if (lf->write_func != logfile_write)
540 			continue;
541 		if (lf->log_mask == log_mask)
542 			return lf;
543 	}
544 	MOWGLI_ITER_FOREACH(n, log_files.head)
545 	{
546 		lf = n->data;
547 		if (lf->write_func != logfile_write)
548 			continue;
549 		if ((lf->log_mask & log_mask) == log_mask)
550 			return lf;
551 	}
552 	return NULL;
553 }
554 
vslog_ext(log_type_t type,unsigned int level,const char * fmt,va_list args)555 static void vslog_ext(log_type_t type, unsigned int level, const char *fmt,
556 		va_list args)
557 {
558 	static bool in_slog = false;
559 	char buf[BUFSIZE];
560 	mowgli_node_t *n;
561 	char datetime[BUFSIZE];
562 	time_t t;
563 	struct tm tm;
564 
565 	if (in_slog)
566 		return;
567 	in_slog = true;
568 
569 	vsnprintf(buf, BUFSIZE, fmt, args);
570 
571 	time(&t);
572 	tm = *localtime(&t);
573 	strftime(datetime, sizeof datetime, "[%Y-%m-%d %H:%M:%S]", &tm);
574 
575 	MOWGLI_ITER_FOREACH(n, log_files.head)
576 	{
577 		logfile_t *lf = (logfile_t *) n->data;
578 
579 		if (type != LOG_ANY && type != lf->log_type)
580 			continue;
581 		if ((lf != log_file || !log_force) && !(level & lf->log_mask))
582 			continue;
583 
584 		return_if_fail(lf->write_func != NULL);
585 
586 		lf->write_func(lf, buf);
587 	}
588 
589 	/*
590 	 * if the event is in the default loglevel, and we are starting, then
591 	 * display it in the controlling terminal.
592 	 */
593 	if (type != LOG_INTERACTIVE && ((runflags & (RF_LIVE | RF_STARTING) &&
594 		(log_file != NULL ? log_file->log_mask : LG_ERROR | LG_INFO) & level) ||
595 		(runflags & RF_LIVE && log_force)))
596 		fprintf(stderr, "%s %s\n", datetime, logfile_strip_control_codes(buf));
597 
598 	in_slog = false;
599 }
600 
slog_ext(log_type_t type,unsigned int level,const char * fmt,...)601 static PRINTFLIKE(3, 4) void slog_ext(log_type_t type, unsigned int level,
602 		const char *fmt, ...)
603 {
604 	va_list args;
605 
606 	va_start(args, fmt);
607 	vslog_ext(type, level, fmt, args);
608 	va_end(args);
609 }
610 
611 /*
612  * slog(unsigned int level, const char *fmt, ...)
613  *
614  * Handles the basic logging of log messages to the log files defined
615  * in the configuration file. All I/O is handled here, and no longer
616  * in the various sourceinfo_t log transforms.
617  *
618  * Inputs:
619  *       - a bitmask of the various log categories this event qualifies to
620  *       - a printf-style format string
621  *       - (optional) additional arguments to be used with that format string
622  *
623  * Outputs:
624  *       - none
625  *
626  * Side Effects:
627  *       - logfiles are updated depending on how they are configured.
628  */
slog(unsigned int level,const char * fmt,...)629 void slog(unsigned int level, const char *fmt, ...)
630 {
631 	va_list args;
632 
633 	va_start(args, fmt);
634 	vslog_ext(LOG_ANY, level, fmt, args);
635 	va_end(args);
636 }
637 
638 /*
639  * logcommand(sourceinfo_t *si, int level, const char *fmt, ...)
640  *
641  * Logs usage of a command from a user or other source (RPC call) as
642  * described by sourceinfo_t.
643  *
644  * Inputs:
645  *       - sourceinfo_t object which describes the source of the command call
646  *       - bitmask of log categories to log the command use to.
647  *       - printf-style format string
648  *       - (optional) additional parameters
649  *
650  * Outputs:
651  *       - none
652  *
653  * Side Effects:
654  *       - qualifying logfile_t objects in log_files are updated.
655  */
logcommand(sourceinfo_t * si,int level,const char * fmt,...)656 void logcommand(sourceinfo_t *si, int level, const char *fmt, ...)
657 {
658 	va_list args;
659 	char lbuf[BUFSIZE];
660 
661 	va_start(args, fmt);
662 	vsnprintf(lbuf, BUFSIZE, fmt, args);
663 	va_end(args);
664 	if (si->su != NULL)
665 		logcommand_user(si->service, si->su, level, "%s", lbuf);
666 	else
667 		logcommand_external(si->service, si->v != NULL ? si->v->description : "unknown", si->connection, si->sourcedesc, si->smu, level, "%s", lbuf);
668 }
669 
670 /*
671  * logcommand_user(service_t *svs, user_t *source, int level, const char *fmt, ...)
672  *
673  * Logs usage of a command from a user as described by sourceinfo_t.
674  *
675  * Inputs:
676  *       - service_t object which describes the service the command is attached to
677  *       - user_t object which describes the source of the command call
678  *       - bitmask of log categories to log the command use to.
679  *       - printf-style format string
680  *       - (optional) additional parameters
681  *
682  * Outputs:
683  *       - none
684  *
685  * Side Effects:
686  *       - qualifying logfile_t objects in log_files are updated.
687  */
logcommand_user(service_t * svs,user_t * source,int level,const char * fmt,...)688 void logcommand_user(service_t *svs, user_t *source, int level, const char *fmt, ...)
689 {
690 	va_list args;
691 	char lbuf[BUFSIZE];
692 	char accountbuf[NICKLEN * 5]; /* entity name len is NICKLEN * 4, plus another for the ID */
693 	bool showaccount;
694 
695 	va_start(args, fmt);
696 	vsnprintf(lbuf, BUFSIZE, fmt, args);
697 	va_end(args);
698 
699 	accountbuf[0] = '\0';
700 	if (source->myuser != NULL)
701 		snprintf(accountbuf, sizeof accountbuf, "%s/%s",
702 				entity(source->myuser)->name, entity(source->myuser)->id);
703 
704 	slog_ext(LOG_NONINTERACTIVE, level, "%s %s:%s!%s@%s[%s] %s",
705 			service_get_log_target(svs),
706 			accountbuf,
707 			source->nick, source->user, source->host,
708 			source->ip != NULL ? source->ip : source->host,
709 			lbuf);
710 	showaccount = source->myuser == NULL || irccasecmp(entity(source->myuser)->name, source->nick);
711 	slog_ext(LOG_INTERACTIVE, level, "%s %s%s%s%s %s",
712 			service_get_log_target(svs),
713 			source->nick,
714 			showaccount ? " (" : "",
715 			showaccount ? (source->myuser ? entity(source->myuser)->name : "") : "",
716 			showaccount ? ")" : "",
717 			lbuf);
718 }
719 
720 /*
721  * logcommand_external(service_t *svs, const char *type, connection_t *source,
722  *       const char *sourcedesc, myuser_t *login, int level, const char *fmt, ...)
723  *
724  * Logs usage of a command from an RPC call as described by sourceinfo_t.
725  *
726  * Inputs:
727  *       - service_t object which describes the service the command is attached to
728  *       - string which describes the type of RPC call
729  *       - connection_t which describes the socket source of the RPC call
730  *       - string which describes the socket source of the RPC call
731  *       - myuser_t which describes the services account used for the RPC call
732  *       - bitmask of log categories to log the command use to.
733  *       - printf-style format string
734  *       - (optional) additional parameters
735  *
736  * Outputs:
737  *       - none
738  *
739  * Side Effects:
740  *       - qualifying logfile_t objects in log_files are updated.
741  */
logcommand_external(service_t * svs,const char * type,connection_t * source,const char * sourcedesc,myuser_t * mu,int level,const char * fmt,...)742 void logcommand_external(service_t *svs, const char *type, connection_t *source, const char *sourcedesc, myuser_t *mu, int level, const char *fmt, ...)
743 {
744 	va_list args;
745 	char lbuf[BUFSIZE];
746 
747 	va_start(args, fmt);
748 	vsnprintf(lbuf, BUFSIZE, fmt, args);
749 	va_end(args);
750 
751 	slog_ext(LOG_NONINTERACTIVE, level, "%s %s:%s(%s)[%s] %s",
752 			service_get_log_target(svs),
753 			mu != NULL ? entity(mu)->name : "",
754 			type,
755 			source != NULL ? source->hbuf : "<noconn>",
756 			sourcedesc != NULL ? sourcedesc : "<unknown>",
757 			lbuf);
758 	slog_ext(LOG_INTERACTIVE, level, "%s <%s>%s %s",
759 			service_get_log_target(svs),
760 			type,
761 			mu != NULL ? entity(mu)->name : "",
762 			lbuf);
763 }
764 
765 /*
766  * logaudit_denycmd(sourceinfo_t *si, command_t *cmd, const char *userlevel)
767  *
768  * Logs a command access denial for auditing.
769  *
770  * Inputs:
771  *       - sourceinfo_t object that was denied
772  *       - command_t object that was denied
773  *       - optional userlevel (if none, will be NULL)
774  *
775  * Outputs:
776  *       - nothing
777  *
778  * Side Effects:
779  *       - qualifying logfile_t objects in log_files are updated
780  */
logaudit_denycmd(sourceinfo_t * si,command_t * cmd,const char * userlevel)781 void logaudit_denycmd(sourceinfo_t *si, command_t *cmd, const char *userlevel)
782 {
783 	slog_ext(LOG_NONINTERACTIVE, LG_DENYCMD, "DENYCMD: [%s] was denied execution of [%s], need privileges [%s %s]",
784 		 get_source_security_label(si), cmd->name, cmd->access, userlevel != NULL ? userlevel : "");
785 	slog_ext(LOG_INTERACTIVE, LG_DENYCMD, "DENYCMD: \2%s\2 was denied execution of \2%s\2, need privileges \2%s %s\2",
786 		 get_source_security_label(si), cmd->name, cmd->access, userlevel != NULL ? userlevel : "");
787 }
788 
789 /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
790  * vim:ts=8
791  * vim:sw=8
792  * vim:noexpandtab
793  */
794