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