3
# $Id: munin-node.in,v 1.12 2004/04/27 21:18:33 jimmyo Exp $
5
# $Log: munin-node.in,v $
6
# Revision 1.12 2004/04/27 21:18:33 jimmyo
7
# Fixed a problem in the node when running as a non-root user and using sudo to run the plugins (Deb#236694).
9
# Revision 1.11 2004/02/05 18:05:59 jimmyo
10
# Improved timeout-handling in node (Deb#224480).
12
# Revision 1.10 2004/02/05 17:35:41 jimmyo
13
# Made client timeouts configurable (not per plugin).
15
# Revision 1.9 2004/02/01 21:34:59 jimmyo
16
# Remove dependency on pgrep (use process groups instead). (SF#881049)
18
# Revision 1.8 2004/02/01 20:46:29 jimmyo
19
# Added better logging of plugin failures in the node. (SF#881045)
21
# Revision 1.7 2004/01/29 19:39:00 jimmyo
22
# Generic plugins now use printf instead of echo -n, as this is more portable (SF#885564)
24
# Revision 1.6 2004/01/29 18:07:52 jimmyo
25
# Bugfix from bug introduced 30 minutes ago
27
# Revision 1.5 2004/01/29 17:36:19 jimmyo
28
# Updated copyright information
30
# Revision 1.4 2004/01/29 16:56:54 jimmyo
31
# Fixed "group" bug. Added support for multiple and optional groups
33
# Revision 1.3 2004/01/17 22:04:29 toreanderson
34
# Change the name in process listing to contain only the path to the munin-node
35
# executable, without '/usr/bin/perl -wT' prepending it.
37
# Revision 1.2 2004/01/15 15:20:01 jimmyo
38
# Making things workable after name change. Upping for test verwion.
40
# Revision 1.1 2004/01/02 18:50:00 jimmyo
41
# Renamed occurrances of lrrd -> munin
43
# Revision 1.1.1.1 2004/01/02 15:18:06 jimmyo
44
# Import of LRRD CVS tree after renaming to Munin
46
# Revision 1.28 2003/12/18 18:51:37 jimmyo
47
# added configuration option "ignore_file", which takes regex for files to ignore (e.g. rpmnew/save) (Deb#224265).
49
# Revision 1.27 2003/12/18 17:58:18 jimmyo
50
# Do a fake clean of the environment because of the taint checking.
52
# Revision 1.26 2003/12/17 21:29:26 jimmyo
53
# Don\'t try to change uid/gid if not running as root. (Deb#224300)
55
# Revision 1.25 2003/12/10 15:30:02 jimmyo
56
# Set path before trying to get hostname
58
# Revision 1.24 2003/12/10 15:11:40 jimmyo
59
# A couple of bugfixes.
61
# Revision 1.23 2003/11/17 09:23:08 jimmyo
62
# Fix taint checking for getting hostname
64
# Revision 1.22 2003/11/17 09:20:09 jimmyo
65
# Fix for machines which don't have "host".
67
# Revision 1.21 2003/11/07 17:43:16 jimmyo
68
# Cleanups and log entries
77
use Net::Server::Fork; # any personality will do
79
# "Clean" environment to disable taint-checking on the environment. We _know_
80
# that the environment is insecure, but we want to let admins shoot themselves
81
# in the foot with it, if they want to.
82
foreach my $key (keys %ENV)
84
$ENV{$key} =~ /^(.*)$/;
88
$0 =~ /^(.*)$/; # for some strange reason won't "$0 = $0;" work.
91
@ISA = qw(Net::Server::Fork);
92
my @ORIG_ARGV = @ARGV;
95
my $servicedir="@@CONFDIR@@/plugins";
96
my $sconfdir="@@CONFDIR@@/plugin-conf.d";
97
my $conffile="@@CONFDIR@@/munin-node.conf";
102
my $VERSION="@@VERSION@@";
103
my $defuser = getpwnam ("nobody");
104
my $defgroup= getgrnam ("munin");
111
GetOptions ( "config=s" => \$conffile,
113
"version!" => \$do_version,
114
"paranoia!" => \$paranoia,
115
"help" => \$do_usage );
119
print "Usage: $0 [options]
122
--help View this message.
123
--config <file> Use <file> as configuration file.
124
[/etc/munin/munin-node.conf]
125
--[no]paranoia Only run plugins owned by root. Check permissions.
127
--debug View debug messages.
128
--version View version information.
136
print "munin-node (munin-node) version $VERSION.
137
Written by Audun Ytterdal, Jimmy Olsen, Tore Anderson / Linpro AS
139
Copyright (C) 2002-2004
140
This is free software released under the GNU Public License. There is NO
141
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
146
# Reset ARGV (for HUPing)
149
# Check permissions of configuration
151
if (!&check_perms ($servicedir) or !&check_perms ($conffile))
153
die "Fatal error. Bailing out.";
156
if (! -f $conffile) {
157
print "ERROR: Cannot open $conffile\n";
161
# A hack to overide the hostname if everyhing thing else fails
162
open FILE,$conffile or die "Cannot open $conffile\n";
165
s/#.*//; # no comments
166
s/^\s+//; # no leading white
167
s/\s+$//; # no trailing white
168
next unless length; # anything left?
170
if (($1 eq "host_name" or $1 eq "hostname") and $2)
174
elsif (($1 eq "default_plugin_user" or $1 eq "default_client_user") and $2)
177
my $defuser = &get_uid ($tmpid);
178
if (! defined ($defuser))
180
die "Default user defined in \"$conffile\" does not exist ($tmpid)";
183
elsif (($1 eq "default_plugin_group" or $1 eq "default_client_group") and $2)
186
$defgroup = &get_gid ($tmpid);
187
if (! defined ($defgroup))
189
die "Default group defined in \"$conffile\" does not exist ($tmpid)";
192
elsif (($1 eq "paranoia") and defined $2)
194
if ("$2" eq "no" or "$2" eq "false" or "$2" eq "off" or "$2" eq "0")
203
elsif (($1 eq "ignore_file") and defined $2)
207
elsif (($1 eq "timeout") and defined $2)
213
$FQDN ||= &get_fq_hostname;
217
MyPackage->run(conf_file => $conffile,
218
pid_file => "@@STATEDIR@@/munin-node.pid");
224
### over-ridden subs below
228
print STDERR "In pre_loop_hook.\n" if $DEBUG;
230
$self->SUPER::pre_loop_hook;
234
print "munins node on $FQDN version: $VERSION\n"
238
for my $node (keys %nodes) {
244
sub get_fq_hostname {
248
$hostname = Net::Domain::hostfqdn();
250
return $hostname if $hostname;
252
$hostname = `hostname`; # Fall$
254
$hostname =~ s/\s//g;
259
if (opendir (DIR,$sconfdir))
262
for my $file (grep { -f "$sconfdir/$_" } readdir (DIR))
264
next if $file =~ m/^\./; # Hidden files
265
next if $file !~ m/^([-\w.]+)$/; # Skip if any weird chars
266
$file = $1; # Not tainted anymore.
267
foreach my $regex (@ignores)
269
next FILES if $file =~ /$regex/;
271
if (!&load_auth_file ($sconfdir, $file, \%sconf))
273
warn "Something wicked happened while reading \"$servicedir/$file\". Check the previous log lines for spesifics.";
279
opendir (DIR,$servicedir) || die "Cannot open plugindir: $servicedir $!";
281
for my $file (grep { -f "$servicedir/$_" } readdir(DIR)) {
282
next if $file =~ m/^\./; # Hidden files
283
next if $file =~ m/.conf$/; # Config files
284
next if $file !~ m/^([-\w.]+)$/; # Skip if any weird chars
285
$file = $1; # Not tainted anymore.
286
foreach my $regex (@ignores)
288
next FILES if $file =~ /$regex/;
290
next if (! -x "$servicedir/$file"); # File not executeable
291
print "file: '$file'\n" if $DEBUG;
293
my @rows = &run_service($file,"config", 1);
294
my $node = &get_var (\%sconf, $file, 'host_name') || $FQDN;
296
for my $row (@rows) {
297
print "row: $row\n" if $DEBUG;
298
if ($row =~ m/^host_name (.+)$/) {
299
print "Found host_name, using it\n" if $DEBUG;
303
$nodes{$node}{$file}=1;
310
for my $line (@lines) {
317
my $node = $_[0] || $FQDN;
318
print join " ", keys %{$nodes{$node}};
324
my @date = localtime (time);
326
printf STDERR ("%d/%02d/%02d-%02d:%02d:%02d %s\n", $date[5]+1900, $date[4]+1,
327
$date[3], $date[2], $date[1], $date[0], $text);
333
return unless $child;
334
if (kill (0, $child))
336
print ("# timeout pid $child - killing...");
337
logger ("Plugin timeout ($timeout seconds): $text (pid $child)");
338
kill (-1, $child); sleep 2;
345
my ($service,$command,$autoreap) = @_;
349
if ($services{$service}) {
352
if ($child = open (CHILD, "-|")) {
354
local $SIG{ALRM} = sub { $timed_out=1; die "$!\n"};
361
reap_children($child, "$service $command: $@");
369
# If Net::Server::Fork is currently taking care of reaping,
370
# we get false errors. Filter them out.
371
unless (defined $autoreap and $autoreap)
373
logger ("Error while executing plugin \"$service\": $!");
378
logger ("Plugin \"$service\" exited with status $?. --@lines--");
384
# New process group...
386
# Setting environment
387
$sconf{$service}{user} = &get_var (\%sconf, $service, 'user');
388
$sconf{$service}{group} = &get_var (\%sconf, $service, 'group');
389
$sconf{$service}{command} = &get_var (\%sconf, $service, 'command');
390
&get_var (\%sconf, $service, 'env', \%{$sconf{$service}{env}});
392
if ($< == 0) # If root...
394
# Giving up gid egid uid euid
395
my $u = (defined $sconf{$service}{'user'}?
396
$sconf{$service}{'user'}:
400
($sconf{$service}{'group'}?" $sconf{$service}{group}":"");
402
print "# Want to run as euid/egid $u/$g\n" if $DEBUG;
404
$( = $g unless $g == 0;
405
$) = $gs unless $g == 0;
406
$< = $u unless $u == 0;
407
$> = $u unless $u == 0;
409
if ($> != $u or $g != (split (' ', $)))[0])
411
print "# Can't drop privileges. Bailing out. (wanted uid=",
412
($sconf{$service}{'user'} || $defuser), " gid=\"",
413
$gs, "\"($g), got uid=$> gid=\"$)\"(",
414
(split (' ', $)))[0], ").\n";
415
logger ("Plugin \"$service\" Can't drop privileges. ".
416
"Bailing out. (wanted uid=".
417
($sconf{$service}{'user'} || $defuser). " gid=\"".
418
$gs. "\"($g), got uid=$> gid=\"$)\"(".
419
(split (' ', $)))[0]. ").\n");
423
print "# Running as uid/gid/euid/egid $</$(/$>/$)\n" if $DEBUG;
424
if (!&check_perms ("$servicedir/$service"))
426
print "# Error: unsafe permissions. Bailing out.";
427
logger ("Error: unsafe permissions. Bailing out.");
431
# Setting environment...
432
if (exists $sconf{$service}{'env'} and
433
defined $sconf{$service}{'env'})
435
foreach my $key (keys %{$sconf{$service}{'env'}})
437
print "# Setting environment $key=$sconf{$service}{env}{$key}\n" if $DEBUG;
438
$ENV{"$key"} = $sconf{$service}{'env'}{$key};
441
if (exists $sconf{$service}{'command'} and
442
defined $sconf{$service}{'command'})
445
foreach my $t (@{$sconf{$service}{'command'}})
449
push (@run, "$servicedir/$service", $command);
456
print STDERR "# About to run \"", join (' ', @run), "\"\n" if $DEBUG;
457
print "# About to run \"", join (' ', @run), "\"\n" if $DEBUG;
462
print "# Execing...\n" if $DEBUG;
463
exec ("$servicedir/$service", $command);
467
print "# Unable to fork.\n";
468
logger ("Unable to fork.");
475
print "# Unknown service\n";
481
sub process_request {
483
print "# munin node at $FQDN\n";
484
local $SIG{ALRM} = sub { logger ("Connection timed out."); die "timeout" };
489
if (m/^list\s*([0-9a-zA-Z\.\-]+)?/) {
492
elsif (/^quit/ || /^\./) {
501
elsif (/^fetch\s?(\S*)/) {
502
print_service (&run_service($1))
504
elsif (/^config\s?(\S*)/) {
505
print_service (&run_service($1,"config"));
507
print "# Unknown command. Try list, nodes, config, fetch, version or quit\n";
515
return undef if (!defined $user);
519
$user = getpwnam ($user);
527
return undef if (!defined $group);
531
$group = getgrnam ($group);
538
my ($dir, $file, $sconf) = @_;
541
if (!defined $dir or !defined $file or !defined $sconf)
546
return undef if (!&check_perms ($dir));
547
return undef if (!&check_perms ("$dir/$file"));
549
if (!open (IN, "$dir/$file"))
551
warn "Could not open file \"$dir/$file\" for reading ($!), skipping plugin\n";
560
print "DEBUG: Config: $service: $_\n" if $DEBUG;
561
if (/^\s*\[([^\]]+)\]\s*$/)
565
elsif (/^\s*user\s+(\S+)\s*$/)
568
$sconf->{$service}{'user'} = &get_uid ($tmpid);
569
print "DEBUG: Config: $service->uid = ", $sconf->{$service}{'user'}, "\n" if $DEBUG;
570
if (!defined $sconf->{$service}{'user'})
572
warn "User \"$tmpid\" in configuration file \"$dir/$file\" nonexistant. Skipping plugin.";
576
elsif (/^\s*group\s+(.+)\s*$/)
579
foreach my $group (split /\s*,\s*/, $tmpid)
583
if ($group =~ /^\(([^)]+)\)$/)
589
my $g = &get_gid ($group);
590
print "DEBUG: Config: $service->gid = ", $sconf->{$service}{'group'}, "\n" if $DEBUG;
591
if (!defined $g and !$optional)
593
warn "Group \"$group\" in configuration file \"$dir/$file\" nonexistant. Skipping plugin.";
596
elsif (!defined $g and $optional)
598
print "DEBUG: Skipping \"$group\" (optional).\n" if $DEBUG;
601
if (!defined $sconf->{$service}{'group'})
603
$sconf->{$service}{'group'} = $g;
607
$sconf->{$service}{'group'} .= " $g";
611
elsif (/^\s*command\s+(.+)\s*$/)
613
@{$sconf->{$service}{'command'}} = split (/\s+/, $1);
615
elsif (/^\s*host_name\s+(.+)\s*$/)
617
$sconf->{$service}{'host_name'} = $1;
619
elsif (/^\s*env\s+([^=\s]+)\s*=\s*(.+)$/)
621
$sconf->{$service}{'env'}{$1} = $2;
622
print "Saving $service->env->$1 = $2...\n" if $DEBUG;
623
warn "Warning: Deprecated format in \"$dir/$file\" under \"[$service]\" (\"env $1=$2\" should be rewritten to \"env.$1 $2\").";
625
elsif (/^\s*env\.(\S+)\s+(.+)$/)
627
$sconf->{$service}{'env'}{$1} = $2;
628
print "Saving $service->env->$1 = $2...\n" if $DEBUG;
630
elsif (/^\s*(\w+)\s+(.+)$/)
632
$sconf->{$service}{'env'}{"lrrd_$1"} = $2;
633
print "Saving $service->env->lrrd_$1 = $2...\n" if $DEBUG;
634
warn "Warning: Deprecated format in \"$dir/$file\" under \"[$service]\" (\"$1 $2\" should be rewritten to \"env lrrd_$1=$2\").";
638
warn "Warning: Unknown config option in \"$dir/$file\" under \"[$service]\": $_";
651
return undef if (!defined $target);
652
return 1 if (!$paranoia);
656
warn "Failed to check permissions on nonexistant target: \"$target\"";
660
@stat = stat ($target);
661
if (!$stat[4] == 0 or
662
($stat[5] != 0 and $stat[2] & 00020) or
665
warn "Warning: \"$target\" has dangerous permissions (", sprintf ("%04o", $stat[2] & 07777), ").";
669
if (-f "$target") # Check dir as well
671
(my $dirname = $target) =~ s/[^\/]+$//;
672
return &check_perms ($dirname);
685
if ($var eq 'env' and !defined $env)
690
if ($var ne 'env' and exists $sconf->{$name}{$var})
692
return $sconf->{$name}{$var};
694
# Deciding environment
695
foreach my $wildservice (grep (/\*$/, reverse sort keys %{$sconf}))
697
(my $tmpservice = $wildservice) =~ s/\*$//;
698
next unless ($name =~ /^$tmpservice/);
699
print "Checking $wildservice...\n" if $DEBUG;
703
if (exists $sconf->{$wildservice}{'env'})
705
foreach my $key (keys %{$sconf->{$wildservice}{'env'}})
707
if (! exists $sconf->{$name}{'env'}{$key})
709
$sconf->{$name}{'env'}{$key} = $sconf->{$wildservice}{'env'}{$key};
710
print "Saving $wildservice->$key\n" if $DEBUG;
717
if (! exists $sconf->{$name}{$var} and
718
exists $sconf->{$wildservice}{$var})
720
return ($sconf->{$wildservice}{$var});
731
munin-node - A daemon to gather information in cooperation with the main
736
munin-node [--options]
742
=item B<< --config <configfile> >>
744
Use E<lt>fileE<gt> as configuration file. [/etc/munin/munin-node.conf]
746
=item B< --[no]paranoia >
748
Only run plugins owned by root. Check permissions as well. [--noparanoia]
752
View this help message.
762
Munin's node is a daemon that Munin connects to fetch data. This data is
763
stored in .rrd-files, and later graphed and htmlified. It's designed to
764
let it be very easy to graph new datasources.
766
Munin-node is a small perlscript listening to port 4949 using
767
Net::Server. It reads all the plugins in /etc/munin/plugins/ on startup.
768
The node accepts the following commands:
772
=item B<< list [node] >>
774
list available plugins for host. If no hostname is specified, list plugins
775
on host running munin-node
779
List nodes that has plugins in this munin-node.
781
=item B<< config <plugin> >>
783
output plugin configuration
785
=item B<< fetch <plugin> >>
801
These plugins can be in you language of choice: bash, perl, python, C. The
802
plugins can be run in two modes: with and without the "config"-parameter. When
803
run with "config" as parameter, the plugin should output the configuration of
804
the graph. When run without parameters, the plugin should output just values
806
# /etc/munin/plugins/load config
808
graph_title Load average
809
graph_args --base 1000 -l 0
816
# /etc/munin/plugins/load
819
For more information, see the documentation section at L<http://munin.sf.net/>.
823
@@CONFDIR@@/munin-node.conf
824
@@CONFDIR@@/plugins/*
825
@@CONFDIR@@/plugin-conf.d/*
826
@@STATEDIR@@/munin-node.pid
827
@@LOGDIR@@/munin-node
831
This is munin-node v@@VERSION@@
835
Audun Ytterdal, Jimmy Olsen, and Tore Anderson.
839
munin-node does, as of now, not check the syntax of the configuration file.
841
Please report other bugs in the bug tracker at L<http://munin.sf.net/>.
845
Copyright � 2002 Audun Ytterdal, Jimmy Olsen, and Tore Anderson / Linpro AS.
847
This is free software; see the source for copying conditions. There is
848
NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR
851
This program is released under the GNU General Public License