Handle hostnames with upper-case letters
[webmin.git] / webalizer / webalizer-lib.pl
1 # webalizer-lib.pl
2 # Common functions for editing the webalizer config file
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7
8 $cron_cmd = "$module_config_directory/webalizer.pl";
9 $custom_logs_file = "$module_config_directory/custom-logs";
10 %access = &get_module_acl();
11
12 # Use sample config if needed
13 if (!-r $config{'webalizer_conf'} && -r $config{'alt_conf'}) {
14         &copy_source_dest($config{'alt_conf'}, $config{'webalizer_conf'});
15         }
16
17 # get_config([logfile])
18 # Parse the webalizer config file for a single logfile or global
19 sub get_config
20 {
21 local $file;
22 if ($_[0]) {
23         $file = &config_file_name($_[0]);
24         }
25 $file = $config{'webalizer_conf'} if (!$file || !-r $file);
26 local @rv;
27 local $lnum = 0;
28 open(FILE, $file);
29 while(<FILE>) {
30         s/\r|\n//g;
31         s/#.*$//;
32         if (/^\s*(\S+)\s+(.*)/) {
33                 push(@rv, { 'name' => $1,
34                             'value' => $2,
35                             'line' => $lnum,
36                             'file' => $file,
37                             'index' => scalar(@rv) });
38                 }
39         $lnum++;
40         }
41 close(FILE);
42 return \@rv;
43 }
44
45 # save_directive(&config, name, [value]*)
46 sub save_directive
47 {
48 local ($conf, $name, @values) = @_;
49 local @old = &find($name, $conf);
50 local $lref = &read_file_lines($conf->[0]->{'file'});
51 local $i;
52 for($i=0; $i<@old || $i<@values; $i++) {
53         if ($i < @old && $i < @values) {
54                 # Just replacing a line
55                 $lref->[$old[$i]->{'line'}] = "$name $values[$i]";
56                 }
57         elsif ($i < @old) {
58                 # Deleting a line
59                 splice(@$lref, $old[$i]->{'line'}, 1);
60                 &renumber($conf, $old[$i]->{'line'}, -1);
61                 }
62         elsif ($i < @values) {
63                 # Adding a line
64                 if (@old) {
65                         # after the last one of the same type
66                         splice(@$lref, $old[$#old]->{'line'}+1, 0,
67                                "$name $values[$i]");
68                         &renumber($conf, $old[$#old]->{'line'}+1, 1);
69                         }
70                 else {
71                         # at end of file
72                         push(@$lref, "$name $values[$i]");
73                         }
74                 }
75         }
76 }
77
78 # renumber(&config, line, offset)
79 sub renumber
80 {
81 foreach $c (@{$_[0]}) {
82         $c->{'line'} += $_[2] if ($c->{'line'} >= $_[1]);
83         }
84 }
85
86 # config_file_name(logfile)
87 sub config_file_name
88 {
89 local $p = $_[0];
90 $p =~ s/^\///;
91 $p =~ s/\//_/g;
92 return "$module_config_directory/$p.conf";
93 }
94
95 # find(name, &config)
96 sub find
97 {
98 local @rv;
99 foreach $c (@{$_[1]}) {
100         push(@rv, $c) if (lc($c->{'name'}) eq lc($_[0]));
101         }
102 return wantarray ? @rv : $rv[0];
103 }
104
105 # find_value(name, &config)
106 sub find_value
107 {
108 local @rv = map { $_->{'value'} } &find(@_);
109 return wantarray ? @rv : $rv[0];
110 }
111
112 # all_log_files(file)
113 # Given a base log file name, returns a list of all log files in the same
114 # directory that start with the same name
115 sub all_log_files
116 {
117 $_[0] =~ /^(.*)\/([^\/]+)$/;
118 local $dir = $1;
119 local $base = $2;
120 local ($f, @rv);
121 opendir(DIR, $dir);
122 foreach $f (readdir(DIR)) {
123         if ($f =~ /^\Q$base\E/ && -f "$dir/$f") {
124                 push(@rv, "$dir/$f");
125                 }
126         }
127 closedir(DIR);
128 return @rv;
129 }
130
131 # get_log_config(path)
132 # Get the configuration for some log file
133 sub get_log_config
134 {
135 local %rv;
136 &read_file(&log_config_name($_[0]), \%rv) || return undef;
137 return \%rv;
138 }
139
140 # save_log_config(path, &config)
141 sub save_log_config
142 {
143 return &write_file(&log_config_name($_[0]), $_[1]);
144 }
145
146 # log_config_name(path)
147 sub log_config_name
148 {
149 local $p = $_[0];
150 $p =~ s/^\///;
151 $p =~ s/\//_/g;
152 return "$module_config_directory/$p.log";
153 }
154
155 # generate_report(file, handle, escape)
156 # Generates a new webalizer report to the configured directory, and sends
157 # any output to the given file handle. Returns 1 if any of the log files
158 # worked OK.
159 sub generate_report
160 {
161 local $h = $_[1];
162 local $lconf = &get_log_config($_[0]);
163 local @all = &all_log_files($_[0]);
164 if (!@all) {
165         print $h "Log file $_[0] does not exist\n";
166         return;
167         }
168 local ($a, %mtime);
169 foreach $a (@all) {
170         local @st = stat($a);
171         $mtime{$a} = $st[9];
172         }
173 local $prog = &get_webalizer_prog();
174 local $type = $lconf->{'type'} == 1 ? "" :
175               $lconf->{'type'} == 2 ? "-F squid" :
176               $lconf->{'type'} == 3 ? "-F ftp" : "";
177 local $cfile = &config_file_name($_[0]);
178 local $conf = -r $cfile ? "-c $cfile" : "";
179 if ($lconf->{'over'} && !&is_readonly_mode()) {
180         unlink("$lconf->{'dir'}/webalizer.current");
181         unlink("$lconf->{'dir'}/webalizer.hist");
182         }
183 unlink("$lconfig->{'dir'}/__db.dns_cache.db");
184 local $user = $lconf->{'user'} || "root";
185 if ($user ne "root" && -r $cfile) {
186         chmod(0644, $cfile);
187         }
188 if (!-d $lconf->{'dir'}) {
189         mkdir($lconf->{'dir'}, 0755);
190         if ($user ne "root") {
191                 local @uinfo = getpwnam($user);
192                 chown($uinfo[2], $uinfo[3], $lconf->{'dir'});
193                 }
194         }
195 local $anyok = 0;
196 foreach $a (sort { $mtime{$a} <=> $mtime{$b} } @all) {
197         local $cmd = "$config{'webalizer'} $conf -o ".
198                      quotemeta($lconf->{'dir'})." $type -p ".quotemeta($a);
199         if ($user ne "root") {
200                 $cmd = &command_as_user($user, 0, $cmd);
201                 }
202         &open_execute_command(OUT, "$cmd 2>&1", 1);
203         while(<OUT>) {
204                 print $h $_[2] ? &html_escape($_) : $_;
205                 }
206         close(OUT);
207         $anyok = 1 if (!$?);
208         &additional_log("exec", undef, $cmd);
209         }
210 return $anyok;
211 }
212
213 # spaced_buttons(button, ...)
214 sub spaced_buttons
215 {
216 local $pc = int(100 / scalar(@_));
217 print "<table width=100%><tr>\n";
218 foreach $b (@_) {
219         local $al = $b eq $_[0] ? "align=left" :
220                     $b eq $_[@_-1] ? "align=right" : "align=center";
221         print "<td width=$pc% $al>$b</td>\n";
222         }
223 print "</table>\n";
224 }
225
226 # read_custom_logs()
227 sub read_custom_logs
228 {
229 open(LOGS, $custom_logs_file);
230 local @rv = map { /^(.*\S)\s+(\S+)/; { 'file' => $1, 'type' => $2 } } <LOGS>;
231 close(LOGS);
232 return @rv;
233 }
234
235 # write_custom_logs(log, ...)
236 sub write_custom_logs
237 {
238 &open_tempfile(LOGS, ">$custom_logs_file");
239 &print_tempfile(LOGS, map { "$_->{'file'} $_->{'type'}\n" } @_);
240 &close_tempfile(LOGS);
241 }
242
243 # can_edit_log(file)
244 sub can_edit_log
245 {
246 foreach $d (split(/\s+/, $access{'dir'})) {
247         local $ok = &is_under_directory($d, $_[0]);
248         return 1 if ($ok);
249         }
250 return 0;
251 }
252
253 # get_webalizer_version(&out)
254 # Returns the Webalizer version number, and puts output into the given scalar
255 # reference.
256 sub get_webalizer_version
257 {
258 local $out = `$config{'webalizer'} -v 2>&1 </dev/null`;
259 if ($out =~ /awffull +(\S+)/) {
260         # AWfull version
261         return $1;
262         }
263 if ($? || $out !~ /\sV(\S+)/) {
264         # Try -V
265         $out = `$config{'webalizer'} -V 2>&1 </dev/null`;
266         }
267 ${$_[0]} = $out;
268 return $out =~ /\sV(\S+)/ ? $1 : undef;
269 }
270
271 # get_webalizer_prog()
272 # Returns either 'webalizer' or 'awffull'
273 sub get_webalizer_prog
274 {
275 return $config{'webalizer'} =~ /awffull/i ? "awffull" : "webalizer";
276 }
277
278 # get_all_logs()
279 # Returns a list of all log files the module can report on
280 sub get_all_logs
281 {
282 # Query apache and squid for their logfiles
283 local %auto = map { $_, 1 } split(/,/, $config{'auto'});
284 local @logs;
285 if (&foreign_installed("apache") && $auto{'apache'}) {
286         &foreign_require("apache", "apache-lib.pl");
287         local $conf = &apache::get_config();
288         local @dirs = ( &apache::find_all_directives($conf, "CustomLog"),
289                         &apache::find_all_directives($conf, "TransferLog") );
290         local $root = &apache::find_directive_struct("ServerRoot", $conf);
291         local $d;
292         foreach $d (@dirs) {
293                 local $lf = $d->{'words'}->[0];
294                 if ($lf =~ /^\|\S+writelogs.pl\s+\S+\s+(\S+)/) {
295                         # Virtualmin log writer .. use real file
296                         $lf = $1;
297                         }
298                 next if ($lf =~ /^\|/);
299                 if ($lf !~ /^\//) {
300                         $lf = "$root->{'words'}->[0]/$lf";
301                         }
302                 open(FILE, $lf);
303                 local $line = <FILE>;
304                 close(FILE);
305                 if (!$line || $line =~ /^([a-zA-Z0-9\.\-\:]+)\s+\S+\s+\S+\s+\[\d+\/[a-zA-z]+\/\d+:\d+:\d+:\d+\s+[0-9\+\-]+\]/) {
306                         push(@logs, { 'file' => $lf,
307                                       'type' => 1 });
308                         }
309                 }
310         }
311
312 # Add log file from Squid
313 if (&foreign_installed("squid") && $auto{'squid'}) {
314         &foreign_require("squid", "squid-lib.pl");
315         local $conf = &squid::get_config();
316         local $log = &squid::find_value("cache_access_log", $conf);
317         $log = "$squid::config{'log_dir'}/access.log"
318                 if (!$log && -d $squid::config{'log_dir'});
319         push(@logs, { 'file' => $log,
320                       'type' => 2 }) if ($log);
321         }
322
323 # Add log file from proftpd
324 if (&foreign_installed("proftpd") && $auto{'proftpd'}) {
325         &foreign_require("proftpd", "proftpd-lib.pl");
326         local $conf = &proftpd::get_config();
327         local $global = &proftpd::find_directive_struct("Global", $conf);
328         local $log = &proftpd::find_directive("TransferLog", $global->{'members'}) || "/var/log/xferlog";
329         push(@logs, { 'file' => $log, 'type' => 3 });
330         }
331
332 # Add log file from wu-ftpd
333 if (&foreign_installed("wuftpd") && $auto{'wuftpd'}) {
334         local %wconfig = &foreign_config("wuftpd");
335         push(@logs, { 'file' => $wconfig{'log_file'}, 'type' => 3 });
336         }
337
338 # Add custom logfiles
339 push(@logs, map { $_->{'custom'} = 1; $_ } &read_custom_logs());
340
341 return @logs;
342 }
343
344 # lconf_to_cron(&lconf, &job)
345 # Copy fields from a webalizer config to a cron job
346 sub lconf_to_cron
347 {
348 local ($lconf, $job) = @_;
349 $job->{'special'} = $lconf->{'special'};
350 $job->{'mins'} = $lconf->{'mins'};
351 $job->{'hours'} = $lconf->{'hours'};
352 $job->{'days'} = $lconf->{'days'};
353 $job->{'months'} = $lconf->{'months'};
354 $job->{'weekdays'} = $lconf->{'weekdays'};
355 }
356
357 1;
358