1 # Functions for parsing the syslog-ng config file
3 BEGIN { push(@INC, ".."); };
7 @log_flags = ( "final", "fallback", "catchall" );
9 # get_syslog_ng_version()
10 # Returns the version number for syslog-ng, or undef
11 sub get_syslog_ng_version
13 local $out = &backquote_command("$config{'syslogng_cmd'} -V 2>&1 </dev/null",1);
14 return $out =~ /syslog-ng\s+([0-9\.]+)/ ? $1 : undef;
17 sub supports_sun_streams
19 return $gconfig{'os_type'} eq 'solaris';
23 # Parses the syslog-ng config file into an array ref of objects
26 if (!scalar(@get_config_cache)) {
27 # First read file into tokens
28 @get_config_cache = &read_config_file($config{'syslogng_conf'});
30 return \@get_config_cache;
33 # read_config_file(file)
34 # Parses a config file into structures
38 local (@rv, @tok, @ltok, @lnum);
39 &open_readfile(CONF, $file);
42 while($line = <CONF>) {
45 $line =~ s/#.*$//g; # Remove hash comment
46 $line =~ s/\/\/.*$//g if ($line !~ /".*\/\/.*"/);
47 $line =~ s/\/\*.*\*\///g; # Remove multi-line comment
48 $line =~ s/^\s*@.*$//g; # Remove @version line
50 if (!$cmode && $line =~ /\/\*/) {
51 # start of a C-style comment
53 $line =~ s/\/\*.*$//g;
56 if ($line =~ /\*\//) {
59 $line =~ s/^.*\*\///g;
61 else { $line = ""; last; }
66 # split line into tokens
69 if ($line =~ /^\s*\"([^"]*)"(.*)$/) {
71 push(@ltok, $1); $line = $2;
73 elsif ($line =~ /^\s*\'([^']*)'(.*)$/) {
75 push(@ltok, $1); $line = $2;
77 elsif ($line =~ /^\s*([{};\(\),\.])(.*)$/) {
79 push(@ltok, $1); $line = $2;
81 elsif ($line =~ /^\s*([0-9\[\]\-]+\.[0-9\[\]\-]+\.[0-9\[\]\-]+\.[0-9\[\]\-]+)(.*)$/) {
83 push(@ltok, $1); $line = $2;
85 elsif ($line =~ /^\s*(\d+\.\d+\.\d+\.\d+)(.*)$/) {
87 push(@ltok, $1); $line = $2;
89 elsif ($line =~ /^\s*([^{};\(\) \t,\.]+)(.*)$/) {
91 push(@ltok, $1); $line = $2;
95 foreach my $t (@ltok) {
102 # parse tokens into data structures
106 local $str = &parse_struct(\@tok, \@lnum, \$i, $j++, $file);
115 # parse_struct(&tokens, &lines, &line_num, index, file)
116 # Reads from the given list of tokens, until one complete structure has been
117 # parsed. If this contains sub-structures, they are parsed too.
120 local (%str, $i, $j, $t, @vals, $str);
122 return undef if ($_[0]->[$i] eq ")"); # end of a parent expression
123 &error("Bad directive ",$_[0]->[$i]," at ",$_[1]->[$i])
124 if ($_[0]->[$i] !~ /^[a-z0-9_\-]+$/i);
125 $str{'name'} = lc($_[0]->[$i]);
126 $str{'line'} = $_[1]->[$i];
127 $str{'index'} = $_[3];
128 $str{'file'} = $_[4];
131 if ($_[0]->[$i] eq "(") {
132 # A directive like: use_dns (no);
133 # or file("/dev/console" owner(root));
134 # Read the first value, and then sub-directives
137 if ($_[0]->[$i] ne ")" && $_[0]->[$i+1] ne "(") {
138 push(@vals, $_[0]->[$i++]);
141 # Parse extra , or .. separated values after (
144 if ($_[0]->[$i] eq ",") {
145 push(@vals, $_[0]->[$i++]);
148 elsif ($_[0]->[$i] eq "." && $_[0]->[$i+1] eq ".") {
150 if ($_[0]->[$i+2] eq ".") {
157 push(@vals, $_[0]->[$i++]);
164 $str{'value'} = $vals[0];
165 $str{'values'} = \@vals;
168 while($_[0]->[$i] ne ")") {
169 if (!defined($_[0]->[$i])) { ${$_[2]} = $i; return undef; }
170 local $str = &parse_struct($_[0], $_[1], \$i, $j++, $_[4]);
172 $i--; # sub-directives don't have a ; at the end
175 $str{'members'} = \@mems;
180 # A directive with children, like: foo bar { smeg(spod); };
181 # These may also form a boolean expression, like :
182 # level(info) or level(debug);
184 # (level(info) or level(debug)) and facility(local7);
185 while($_[0]->[$i] ne "{") {
186 # Parse stuff before {
187 push(@vals, $_[0]->[$i++]);
189 $str{'values'} = \@vals;
190 $str{'value'} = $vals[0];
193 # Parse the sub-structures
197 while($_[0]->[$i] ne "}") {
198 if (!defined($_[0]->[$i])) { ${$_[2]} = $i; return undef; }
199 if ($_[0]->[$i] eq "(" || $_[0]->[$i] eq ")") {
200 # Start of a sub-expression
201 push(@mems, $_[0]->[$i++]);
203 elsif ($_[0]->[$i] eq "and" || $_[0]->[$i] eq "or" ||
204 $_[0]->[$i] eq "not") {
205 # A separator between directives
206 push(@mems, $_[0]->[$i++]);
208 elsif ($_[0]->[$i] eq ";") {
209 # Left-over ; , in an expression like level(foo));
213 # An actual directive
214 local $str = &parse_struct($_[0], $_[1], \$i, $j++, $_[4]);
216 if ($_[0]->[$i-1] ne ";") {
217 # This wasn't the last directive
223 $str{'members'} = \@mems;
224 $i += 2; # skip trailing } and ;
226 $str{'eline'} = $_[1]->[$i-1]; # ending line is the line number the trailing
232 # save_directive(&config, &parent, name|&old, &new, no-write)
233 # Updates, creates or deletes a directive in the syslog-ng config
236 local ($conf, $parent, $name, $value, $nowrite) = @_;
238 local $new = !$value ? undef : ref($value) ? $value :
240 'values' => [ $value ],
243 # Read the config file and work out the lines used
245 $lref = &read_file_lines($config{'syslogng_conf'}) if (!$nowrite);
246 local ($mems, $memseline);
248 $mems = $parent->{'members'};
249 $memseline = $parent->{'eline'};
254 foreach my $c (@$conf) {
255 $memseline = $c->{'eline'} if ($c->{'eline'} > $memseline);
258 local $old = !$name ? undef : ref($name) ? $name : scalar(&find($name, $mems));
259 local ($idx, $oldlen, $newlen, @lines);
261 $idx = &indexof($old, @$mems);
262 $idx >= 0 || &error("Failed to find $old in array of ",scalar(@$mems));
263 $oldlen = $old->{'eline'} - $old->{'line'} + 1;
266 @lines = &directive_lines($new);
267 $newlen = scalar(@lines);
271 # Update the directive
272 $new->{'line'} = $old->{'line'};
273 $new->{'eline'} = $new->{'line'}+$newlen-1;
276 local $idx = &indexof($old, @$mems);
277 $mems->[$idx] = $new;
280 # Update it in the file
281 &renumber($conf, $new->{'line'}, $newlen - $oldlen);
282 splice(@$lref, $old->{'line'}, $oldlen, @lines);
286 elsif ($old && !$new) {
287 # Remove the directive
288 splice(@$mems, $idx, 1);
290 # Remove from the file
291 &renumber($conf, $old->{'line'}, -$oldlen);
292 splice(@$lref, $old->{'line'}, $oldlen);
295 elsif (!$old && $new) {
297 $new->{'line'} = $memseline+1;
298 $new->{'eline'} = $memseline+$newlen;
300 # Insert into the file
301 &renumber($conf, $new->{'line'}, $newlen);
302 splice(@$lref, $new->{'line'}, 0, @lines);
306 &flush_file_lines($config{'syslogng_conf'}) if (!$nowrite);
309 # save_multiple_directives(&conf, &parent, &oldlist, &newlist, no-write)
310 # A convenience function to update multiple directives at once
311 sub save_multiple_directives
313 local ($conf, $parent, $oldlist, $newlist, $nowrite) = @_;
314 for(my $i=0; $i<@$oldlist || $i<@$newlist; $i++) {
315 local $what = $oldlist->[$i] || $newlist->[$i];
316 &save_directive($conf, $parent, $oldlist->[$i], $newlist->[$i],
321 # renumber(&conf, line, offset)
322 # Changes the line numbers of all directives AFTER the given line
325 local ($conf, $line, $offset) = @_;
326 foreach my $c (@$conf) {
327 $c->{'line'} += $offset if ($c->{'line'} > $line);
328 $c->{'eline'} += $offset if ($c->{'eline'} > $line);
329 &renumber($c->{'members'}, $line, $offset) if ($c->{'members'});
333 # directive_lines(&dir)
334 # Returns an array of lines used by some directive, which may be a single
335 # value, or have sub-members
340 if ($dir->{'type'} == 0) {
341 # A directive like use_dns(no); or file("/dev/console" owner(root));
342 local $line = $dir->{'name'}."(";
343 foreach my $v (@{$dir->{'values'}}) {
344 $line .= "ed_value($v)." ";
347 foreach my $m (@{$dir->{'members'}}) {
348 local ($mline) = &directive_lines($m);
355 elsif ($dir->{'type'} == 1) {
356 # A directive with children, like: foo bar { smeg(spod); };
357 local $line = $dir->{'name'};
358 foreach my $v (@{$dir->{'values'}}) {
359 $line .= " "."ed_value($v);
364 foreach my $m (@{$dir->{'members'}}) {
366 # An actual directive
367 local @mlines = &directive_lines($m);
373 # Previous one doesn't need a ;
374 $w[$#w] =~ s/\s*;\s*$//;
379 if ($dir->{'name'} eq 'filter') {
381 local $line = join(" ", @w);
382 $line .= ";" if ($line && $line !~ /\s*;\s*$/);
383 push(@rv, " ".$line);
386 # Each directive is on its own line
387 push(@rv, map { " ".$_ } @w);
394 # quoted_value(string)
395 # Returns some string with quotes around it, if needed
399 return $str =~ /^[a-z\_][a-z0-9\_]*$/i ? $str :
400 $str =~ /^\d+\.\d+\.\d+\.\d+$/ ? $str :
401 $str =~ /^[0-9\[\]\-]+\.[0-9\[\]\-]+\.[0-9\[\]\-]+\.[0-9\[\]\-]+$/ ? $str :
402 $str eq "," || $str eq ".." ? $str :
403 $str =~ /^\d+$/ ? $str :
404 $str =~ /\"/ ? "'$str'" : "\"$str\"";
411 foreach $c (@{$_[1]}) {
412 if ($c->{'name'} eq $_[0]) {
416 return @rv ? wantarray ? @rv : $rv[0]
417 : wantarray ? () : undef;
420 # find_value(name, &array)
424 @v = &find($_[0], $_[1]);
425 if (!@v) { return undef; }
426 elsif (wantarray) { return map { $_->{'value'} } @v; }
427 else { return $v[0]->{'value'}; }
430 sub is_syslog_ng_running
432 if ($config{'pid_file'}) {
433 return &check_pid_file($config{'pid_file'});
436 return &find_byname("syslog-ng");
440 # nice_destination_type(&dest)
441 # Returns a human-readable destination type
442 sub nice_destination_type
445 local $file = &find_value("file", $d->{'members'});
446 local $usertty = &find_value("usertty", $d->{'members'});
447 local $program = &find_value("program", $d->{'members'});
448 local $pipe = &find_value("pipe", $d->{'members'});
449 local $udp = &find_value("udp", $d->{'members'});
450 local $tcp = &find_value("tcp", $d->{'members'});
451 local $dgram = &find_value("unix-dgram", $d->{'members'});
452 local $stream = &find_value("unix-stream", $d->{'members'});
453 return $file ? ($text{'destinations_typef'}, 0) :
454 $usertty ? ($text{'destinations_typeu'}, 1) :
455 $program ? ($text{'destinations_typep'}, 2) :
456 $pipe ? ($text{'destinations_typei'}, 3) :
457 $udp ? ($text{'destinations_typed'}, 4) :
458 $tcp ? ($text{'destinations_typet'}, 5) :
459 $dgram ? ($text{'destinations_typeg'}, 6) :
460 $stream ? ($text{'destinations_types'}, 7) : (undef, -1);
463 # nice_destination_file(&dest)
464 # Returns a human-readable destination filename / hostname / etc
465 sub nice_destination_file
468 local $file = &find_value("file", $d->{'members'});
469 local $usertty = &find_value("usertty", $d->{'members'});
470 local $program = &find_value("program", $d->{'members'});
471 local $pipe = &find_value("pipe", $d->{'members'});
472 local $udp = &find_value("udp", $d->{'members'});
473 local $tcp = &find_value("tcp", $d->{'members'});
474 local $dgram = &find_value("unix-dgram", $d->{'members'});
475 local $stream = &find_value("unix-stream", $d->{'members'});
476 return $file ? "<tt>$file</tt>" :
477 $program ? "<tt>$program</tt>" :
478 $pipe ? "<tt>$pipe</tt>" :
479 $tcp ? &text('destinations_host', "<tt>$tcp</tt>") :
480 $udp ? &text('destinations_host', "<tt>$udp</tt>") :
481 $dgram ? "<tt>$dgram</tt>" :
482 $stream ? "<tt>$stream</tt>" :
483 $usertty eq "*" ? $text{'destinations_allusers'} :
484 $usertty ? &text('destinations_users', "<tt>$usertty</tt>") :
490 local ($source) = @_;
492 local $internal = &find("internal", $source->{'members'});
494 push(@rv, $text{'sources_typei'});
496 foreach my $t ("unix-stream", "unix-dgram") {
497 local $unix = &find($t, $source->{'members'});
498 local $msg = $t eq "unix-stream" ? 'sources_types' : 'sources_typed';
500 push(@rv, $text{$msg}." <tt>$unix->{'value'}</tt>");
503 foreach my $t ('tcp', 'udp') {
504 local $net = &find($t, $source->{'members'});
505 local $msg = $t eq "tcp" ? 'sources_typet' : 'sources_typeu';
507 push(@rv, $text{$msg});
510 local $file = &find("file", $source->{'members'});
512 push(@rv, $text{'sources_typef'}." <tt>$file->{'value'}</tt>");
514 local $pipe = &find("pipe", $source->{'members'});
516 push(@rv, $text{'sources_typep'}." <tt>$pipe->{'value'}</tt>");
518 local $sun_streams = &find("sun-streams", $source->{'members'});
520 push(@rv, $text{'sources_typen'}." <tt>$sun_streams->{'value'}</tt>");
522 return join(", ", @rv);
525 # check_dependencies(type, name)
526 # Returns a list of log objects that use some named source, destination or
528 sub check_dependencies
530 local ($type, $name) = @_;
531 local $conf = &get_config();
532 local @logs = &find("log", $conf);
534 foreach my $l (@logs) {
535 local @deps = &find($type, $l->{'members'});
536 foreach my $d (@deps) {
537 if ($d->{'value'} eq $name) {
546 # rename_dependencies(type, old, new)
547 # Updates any log objects that use the old named type to use the new
548 sub rename_dependencies
550 local ($type, $oldname, $newname) = @_;
551 #return if ($oldname eq $newname);
552 local $conf = &get_config();
553 local @logs = &find("log", $conf);
555 foreach my $l (@logs) {
556 local @deps = &find($type, $l->{'members'});
558 foreach my $d (@deps) {
559 if ($d->{'value'} eq $oldname) {
560 $d->{'values'} = [ $newname ];
561 #&save_directive($conf, $l, $d, $d, 1);
566 &save_directive($conf, undef, $l, $l, 0);
571 # all_log_files(file)
572 # Given a filename, returns all rotated versions, ordered by oldest first
575 $_[0] =~ /^(.*)\/([^\/]+)$/;
579 opendir(DIR, &translate_filename($dir));
580 foreach $f (readdir(DIR)) {
581 local $trans = &translate_filename("$dir/$f");
582 if ($f =~ /^\Q$base\E/ && -f $trans) {
583 push(@rv, "$dir/$f");
584 $mtime{"$dir/$f"} = [ stat($trans) ];
588 return sort { $mtime{$a}->[9] <=> $mtime{$b}->[9] } @rv;
591 # catter_command(file)
592 # Given a file that may be compressed, returns the command to output it in
593 # plain text, or undef if impossible
597 local $q = quotemeta($l);
598 if ($l =~ /\.gz$/i) {
599 return &has_command("gunzip") ? "gunzip -c $q" : undef;
601 elsif ($l =~ /\.Z$/i) {
602 return &has_command("uncompress") ? "uncompress -c $q" : undef;
604 elsif ($l =~ /\.bz2$/i) {
605 return &has_command("bunzip2") ? "bunzip2 -c $q" : undef;
612 # nice_filter_desc(&filter)
613 # Returns a human-readable description for a filter
616 local ($filter) = @_;
618 foreach my $m (@{$filter->{'members'}}) {
620 # A condition like level, facility or match
621 local @v = @{$m->{'values'}};
622 if ($m->{'name'} eq 'level') {
624 push(@rv, &text('filters_priorities',
628 @v = grep { $_ ne "," } @v;
629 push(@rv, &text('filters_priorities2',
633 push(@rv, &text('filters_priority', $v[0]));
636 elsif ($m->{'name'} eq 'facility') {
638 @v = grep { $_ ne "," } @v;
639 push(@rv, &text('filters_facilities',
643 push(@rv, &text('filters_facility', $v[0]));
646 elsif ($m->{'name'} eq 'match') {
647 push(@rv, &text('filters_match', $v[0]));
649 elsif ($m->{'name'} eq 'program') {
650 push(@rv, &text('filters_program', $v[0]));
652 elsif ($m->{'name'} eq 'host') {
653 push(@rv, &text('filters_host', $v[0]));
655 elsif ($m->{'name'} eq 'netmask') {
656 push(@rv, &text('filters_netmask', $v[0]));
660 push(@rv, $m->{'name'}."(".join(",", @v).")");
669 @rv = ( @rv[0..7], "..." );
671 return join(" ", @rv);
675 # Returns a list of all priorities
678 return ( 'debug', 'info', 'notice', 'warning',
679 'err', 'crit', 'alert', 'emerg' );
684 return ('auth', 'authpriv', 'cron', 'daemon', 'kern', 'lpr', 'mail', 'mark', 'news', 'syslog', 'user', 'uucp', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7');
687 # apply_configuration()
688 # Activate the current config with a HUP signal
689 sub apply_configuration
691 local $pid = &check_pid_file($config{'pid_file'});
693 &kill_logged('HUP', $pid);
697 return $text{'apply_egone'};
702 # Tell the syslog server to re-open it's log files
705 &apply_configuration();
709 # Attempts to start the syslog server process, and returns undef on success
710 # or an error message on failure
713 local $cmd = $config{'start_cmd'} ||
714 "$config{'syslogng_cmd'} -f ".quotemeta($config{'syslogng_conf'}).
715 " -p ".quotemeta($config{'pid_file'});
716 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
717 return $? ? "<pre>$out</pre>" : undef;
721 # Attempts to stop the syslog server process, and returns undef on success
722 # or an error message on failure
725 if ($config{'stop_cmd'}) {
726 local $out = &backquote_logged("$config{'stop_cmd'} 2>&1 </dev/null");
727 return $? ? "<pre>$out</pre>" : undef;
730 local $pid = &check_pid_file($config{'pid_file'});
732 &kill_logged('TERM', $pid);
736 return $text{'apply_egone'};
741 # get_other_module_logs([module])
742 # Returns a list of logs supplied by other modules
743 sub get_other_module_logs
748 foreach my $minfo (&get_all_module_infos()) {
749 next if ($mod && $minfo->{'dir'} ne $mod);
750 next if (!$minfo->{'syslog'});
751 next if (!&foreign_installed($minfo->{'dir'}));
752 local $mdir = &module_root_directory($minfo->{'dir'});
753 next if (!-r "$mdir/syslog_logs.pl");
754 &foreign_require($minfo->{'dir'}, "syslog_logs.pl");
756 foreach my $l (&foreign_call($minfo->{'dir'}, "syslog_getlogs")) {
757 local $fc = $l->{'file'} || $l->{'cmd'};
758 next if ($done{$fc}++);
759 $l->{'minfo'} = $minfo;
760 $l->{'mod'} = $minfo->{'dir'};
761 $l->{'mindex'} = $j++;
765 @rv = sort { $a->{'minfo'}->{'desc'} cmp $b->{'minfo'}->{'desc'} } @rv;
767 foreach my $l (@rv) {
768 $l->{'index'} = $i++;