Handle hostnames with upper-case letters
[webmin.git] / squid / squid-lib.pl
1 # squid-lib.pl
2 # Functions for configuring squid.conf
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 do 'parser-lib.pl';
8 %access = &get_module_acl();
9 $auth_program = "$module_config_directory/squid-auth.pl";
10 $auth_database = "$module_config_directory/users";
11 @caseless_acl_types = ( "url_regex", "urlpath_regex", "proxy_auth_regex",
12                         "srcdom_regex", "dstdom_regex", "ident_regex" );
13
14 # Get the squid version
15 if (open(VERSION, "$module_config_directory/version")) {
16         chop($squid_version = <VERSION>);
17         close(VERSION);
18         }
19
20 # choice_input(text, name, &config, default, [display, option]+)
21 # Display a number of radio buttons for selecting some option
22 sub choice_input
23 {
24 local($v, $vv, $rv, $i);
25 $v = &find_config($_[1], $_[2]);
26 $vv = $v ? $v->{'value'} : $_[3];
27 $rv = "<td><b>$_[0]</b></td> <td valign=top>";
28 for($i=4; $i<@_; $i+=2) {
29         $rv .= "<input type=radio name=$_[1] value=\"".$_[$i+1]."\" ".
30                 ($vv eq $_[$i+1] ? "checked" : "")."> $_[$i]\n";
31         }
32 return $rv."</td>\n";
33 }
34
35 # select_input(text, name, &config, default, [display, option]+)
36 # Like choice_input, but uses a drop-down select field
37 sub select_input
38 {
39 local($v, $vv, $rv, $i);
40 $v = &find_config($_[1], $_[2]);
41 $vv = $v ? $v->{'value'} : $_[3];
42 $rv = "<td><b>$_[0]</b></td> <td valign=top><select name=$_[1]>";
43 for($i=4; $i<@_; $i+=2) {
44         $rv .= "<option value=\"".$_[$i+1]."\" ".
45                 ($vv eq $_[$i+1] ? "selected" : "")."> $_[$i]\n";
46         }
47 return $rv."</select></td>\n";
48 }
49
50 # save_choice(name, default, &config)
51 # Save a selection from choice_input()
52 sub save_choice
53 {
54 if ($in{$_[0]} eq $_[1]) { &save_directive($_[2], $_[0], [ ]); }
55 else { &save_directive($_[2], $_[0], [{ 'name' => $_[0],
56                                         'values' => [ $in{$_[0]} ] }]); }
57 }
58
59 # list_input(text, name, &config, type, [default])
60 # Display a list of values
61 sub list_input
62 {
63 local($v, $rv, @av);
64 foreach $v (&find_config($_[1], $_[2])) {
65         push(@av, @{$v->{'values'}});
66         }
67 if ($_[4]) {
68         $opt = sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[4]\n",
69                 @av ? "" : "checked";
70         $opt .= sprintf "<input type=radio name=$_[1]_def value=0 %s>\n",
71                 @av ? "checked" : "";
72         }
73 if ($_[3] == 0) {
74         # text area
75         $rv = "<td valign=top><b>$_[0]</b></td> <td valign=top>";
76         if ($opt) { $rv .= "$opt Listed..<br>\n"; }
77         $rv .= "<textarea name=$_[1] rows=3 cols=15>".
78                 join("\n", @av)."</textarea></td>\n";
79         }
80 else {
81         # one long text field
82         $rv = "<td valign=top><b>$_[0]</b></td> <td colspan=3 valign=top>$opt";
83         $rv .= "<input name=$_[1] size=50 value=\"".join(' ',@av)."\"></td>\n";
84         }
85 return $rv;
86 }
87
88 # save_list(name, &checkfunc, &config)
89 sub save_list
90 {
91 local($v, @vals, $err);
92 if (!$in{"$_[0]_def"}) {
93         @vals = split(/\s+/, $in{$_[0]});
94         if ($_[1]) {
95                 foreach $v (@vals) {
96                         &check_error($_[1], $v);
97                         }
98                 }
99         }
100 if (@vals) { &save_directive($_[2], $_[0],
101                 [{ 'name' => $_[0], values => \@vals }]); }
102 else { &save_directive($_[2], $_[0], [ ]); }
103 }
104
105 # check_error(&function, value)
106 sub check_error
107 {
108 return if (!$_[0]);
109 local $err = &{$_[0]}($_[1]);
110 if ($err) { &error($err); }
111 }
112
113 # address_input(text, name, &config, type)
114 # Display a text area for entering 0 or more addresses
115 sub address_input
116 {
117 local($v, $rv, @av);
118 foreach $v (&find_config($_[1], $_[2])) {
119         push(@av, @{$v->{'values'}});
120         }
121 if ($_[3] == 0) {
122         # text area
123         $rv = "<td valign=top><b>$_[0]</b></td> <td valign=top>";
124         $rv .= "<textarea name=$_[1] rows=3 cols=15>".
125                 join("\n", @av)."</textarea></td>\n";
126         }
127 else {
128         # one long text field
129         $rv = "<td valign=top><b>$_[0]</b></td> <td colspan=3 valign=top>";
130         $rv .= "<input name=$_[1] size=50 value=\"".join(' ',@av)."\"></td>\n";
131         }
132 return $rv;
133 }
134
135 # save_address(name, config)
136 sub save_address
137 {
138 local($addr, @vals);
139 foreach $addr (split(/\s+/, $in{$_[0]})) {
140         &check_ipaddress($addr) || &error(&text('lib_emsg1',$addr));
141         push(@vals, $addr);
142         }
143 if (@vals) { &save_directive($_[1], $_[0],
144                 [{ 'name' => $_[0], values => \@vals }]); }
145 else { &save_directive($_[1], $_[0], [ ]); }
146 }
147
148 # opt_input(text, name, &config, default, size, units)
149 # Display an optional field for entering something
150 sub opt_input
151 {
152 local($v, $rv);
153 $v = &find_config($_[1], $_[2]);
154 $rv = "<td valign=top><b>$_[0]</b></td> <td valign=top nowrap";
155 $rv .= $_[4] > 30 ? " colspan=3>\n" : ">\n";
156 $rv .= sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[3]\n",
157         $v ? "" : "checked";
158 $rv .= sprintf "<input type=radio name=$_[1]_def value=0 %s> ",
159         $v ? "checked" : "";
160 $rv .= sprintf "<input name=$_[1] size=$_[4] value=\"%s\"> %s</td>\n",
161         $v ? $v->{'value'} : "", $_[5];
162 return $rv;
163 }
164
165 # save_opt(name, &function, &config)
166 # Save an input from opt_input()
167 sub save_opt
168 {
169 if ($in{"$_[0]_def"}) { &save_directive($_[2], $_[0], [ ]); }
170 else {
171         &check_error($_[1], $in{$_[0]});
172         local $dir = { 'name' => $_[0], 'values' => [ $in{$_[0]} ] };
173         &save_directive($_[2], $_[0], [ $dir ]);
174         }
175 }
176
177 # opt_time_input(text, name, &config, default, size)
178 sub opt_time_input
179 {
180 local($v, $rv, $u, %ts );
181 $v = &find_config($_[1], $_[2]);
182 $rv = "<td valign=top><b>$_[0]</b></td> <td valign=top nowrap>\n";
183 $rv .= sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[3]\n",
184         $v ? "" : "checked";
185 $rv .= sprintf "<input type=radio name=$_[1]_def value=0 %s> ",
186         $v ? "checked" : "";
187 $rv .= &time_fields($_[1], $_[4], $v ? @{$v->{'values'}} : ( ));
188 $rv .= "</td>\n";
189 return $rv;
190 }
191
192 # time_field(name, size, time, units)
193 sub time_fields
194 {
195 local ($rv, %ts);
196 %ts = ( "second"=>      $text{"lib_seconds"},
197         "minute"=>      $text{"lib_minutes"},
198         "hour"=>        $text{"lib_hours"},
199         "day"=>         $text{"lib_days"},
200         "week"=>        $text{"lib_weeks"},
201         "fortnight"=>   $text{"lib_fortnights"},
202         "month"=>       $text{"lib_months"},
203         "year"=>        $text{"lib_years"},
204         "decade"=>      $text{"lib_decades"} );
205 $rv .= sprintf "<input name=$_[0] size=$_[1] value=\"%s\">\n", $_[2];
206 $rv .= "<select name=$_[0]_u>\n";
207 foreach $u (keys %ts) {
208         $rv .= sprintf "<option value=$u %s>$ts{$u}\n",
209                 $_[3] =~ /^$u/ ? "selected" : "";
210         }
211 $rv .= "</select>\n";
212 return $rv;
213 }
214
215 # save_opt_time(name, &config)
216 sub save_opt_time
217 {
218 local %ts = ( "second"=>      $text{"lib_seconds"},
219         "minute"=>      $text{"lib_minutes"},
220         "hour"=>        $text{"lib_hours"},
221         "day"=>         $text{"lib_days"},
222         "week"=>        $text{"lib_weeks"},
223         "fortnight"=>   $text{"lib_fortnights"},
224         "month"=>       $text{"lib_months"},
225         "year"=>        $text{"lib_years"},
226         "decade"=>      $text{"lib_decades"} );
227
228 if ($in{"$_[0]_def"}) { &save_directive($_[1], $_[0], [ ]); }
229 elsif ($in{$_[0]} !~ /^[0-9\.]+$/) {
230         &error(&text('lib_emsg2', $in{$_[0]}, $ts{$in{"$_[0]_u"}}) );
231         }
232 else {
233         local $dir = { 'name' => $_[0],
234                        'values' => [ $in{$_[0]}, $in{"$_[0]_u"} ] };
235         &save_directive($_[1], $_[0], [ $dir ]);
236         }
237 }
238
239 # opt_bytes_input(text, name, &config, default, size)
240 sub opt_bytes_input
241 {
242 local($v, $rv, $u, %ss);
243 @ss = ( [ "KB", $text{'lib_kb'} ],
244         [ "MB", $text{'lib_mb'} ],
245         [ "GB", $text{'lib_gb'} ] );
246 $v = &find_config($_[1], $_[2]);
247 $rv = "<td valign=top><b>$_[0]</b></td> <td valign=top nowrap>\n";
248 $rv .= sprintf "<input type=radio name=$_[1]_def value=1 %s> $_[3]\n",
249         $v ? "" : "checked";
250 $rv .= sprintf "<input type=radio name=$_[1]_def value=0 %s> ",
251         $v ? "checked" : "";
252 $rv .= sprintf "<input name=$_[1] size=$_[4] value=\"%s\">\n",
253         $v ? $v->{'values'}->[0] : "";
254 $rv .= "<select name=$_[1]_u>\n";
255 foreach $u (@ss) {
256         $rv .= sprintf "<option value=$u->[0] %s>$u->[1]\n",
257                 $v && $v->{'values'}->[1] eq $u->[0] ? "selected" : "";
258         }
259 $rv .= sprintf "<option value='' %s>bytes\n",
260         $v && $v->{'values'}->[1] eq "" ? "selected" : "";
261 $rv .= "</select></td>\n";
262 return $rv;
263 }
264
265 # save_opt_bytes(name, &config)
266 sub save_opt_bytes
267 {
268 local %ss = ( "KB"=>  $text{'lib_kb'},
269         "MB"=>  $text{'lib_mb'},
270         "GB"=>  $text{'lib_gb'} );
271
272 if ($in{"$_[0]_def"}) { &save_directive($_[1], $_[0], [ ]); }
273 elsif ($in{$_[0]} !~ /^[0-9\.]+$/) {
274         &error(&text('lib_emsg3', $in{$_[0]}, $ss{$in{"$_[0]_u"}}) );
275         }
276 else {
277         local $dir = { 'name' => $_[0],
278                        'values' => [ $in{$_[0]}, $in{"$_[0]_u"} ] };
279         &save_directive($_[1], $_[0], [ $dir ]);
280         }
281 }
282
283 %acl_types = ("src", $text{'lib_aclca'},
284               "dst", $text{'lib_aclwsa'},
285               "srcdomain", $text{'lib_aclch'},
286               "dstdomain", $text{'lib_aclwsh'},
287               "time", $text{'lib_acldat'},
288               "url_regex", $text{'lib_aclur'},
289               "urlpath_regex", $text{'lib_aclupr'},
290               "port", $text{'lib_aclup'},
291               "proto", $text{'lib_aclup1'},
292               "method", $text{'lib_aclrm'},
293               "browser", $text{'lib_aclbr'},
294               "user", $text{'lib_aclpl'},
295               "arp", $text{'lib_aclarp'} );
296 if ($squid_version >= 2.0) {
297         $acl_types{'src_as'} = $text{'lib_aclsan'};
298         $acl_types{'dst_as'} = $text{'lib_acldan'};
299         $acl_types{'proxy_auth'} = $text{'lib_aclea'};
300         $acl_types{'srcdom_regex'} = $text{'lib_aclcr'};
301         $acl_types{'dstdom_regex'} = $text{'lib_aclwsr'};
302         }
303 if ($squid_version >= 2.2) {
304         $acl_types{'ident'} = $text{'lib_aclru'};
305         $acl_types{'myip'} = $text{'lib_aclpia'};
306         delete($acl_types{'user'});
307         }
308 if ($squid_version >= 2.3) {
309         $acl_types{'maxconn'} = $text{'lib_aclmc'};
310         $acl_types{'myport'} = $text{'lib_aclpp'};
311         $acl_types{'snmp_community'} = $text{'lib_aclsc'};
312         }
313 if ($squid_version >= 2.4) {
314         $acl_types{'req_mime_type'} = $text{'lib_aclrmt'};
315         $acl_types{'proxy_auth_regex'} = $text{'lib_aclear'};
316         }
317 if ($squid_version >= 2.5) {
318         $acl_types{'rep_mime_type'} = $text{'lib_aclrpmt'};
319         $acl_types{'ident_regex'} = $text{'lib_aclrur'};
320         $acl_types{'external'} = $text{'lib_aclext'};
321         $acl_types{'max_user_ip'} = $text{'lib_aclmuip'};
322         }
323
324 # restart_button()
325 # Returns HTML for a link to put in the top-right corner of every page
326 sub restart_button
327 {
328 return undef if ($config{'restart_pos'} == 2);
329 local $pid = &is_squid_running();
330 local $args = "redir=".&urlize(&this_url())."&pid=$pid";
331 if ($pid) {
332         return ($access{'restart'} ? "<a href=\"restart.cgi?$args\">$text{'lib_buttac'}</a><br>\n" : "").
333                ($access{'start'} ? "<a href=\"stop.cgi?$args\">$text{'lib_buttss'}</a>\n" : "");
334         }
335 else {
336         return $access{'start'} ? "<a href=\"start.cgi?$args\">$text{'lib_buttss1'}</a>\n" : "";
337         }
338 }
339
340 # is_squid_running()
341 # Returns the process ID if squid is running
342 sub is_squid_running
343 {
344 local $conf = &get_config();
345 local ($pidstruct, $pidfile);
346 $pidstruct = &find_config("pid_filename", $conf);
347 if (!$pidstruct) {
348         $pidstruct = &find_config("pid_filename", $conf, 2);
349         }
350 $pidfile = $pidstruct ? $pidstruct->{'values'}->[0] : $config{'pid_file'};
351 if ($pidfile eq "none" || !$pidfile) {
352         local ($pid) = &find_byname("squid");
353         return $pid;
354         }
355 else {
356         return &check_pid_file($pidfile);
357         }
358 }
359
360 # this_url()
361 # Returns the URL in the apache directory of the current script
362 sub this_url
363 {
364 local($url);
365 $url = $ENV{'SCRIPT_NAME'};
366 if (defined($ENV{'QUERY_STRING'})) { $url .= "?$ENV{'QUERY_STRING'}"; }
367 return $url;
368 }
369
370 # list_auth_users(file)
371 sub list_auth_users
372 {
373 local(@rv, $lnum); $lnum = 0;
374 open(USERS, $_[0]);
375 while(<USERS>) {
376         if (/^(#*)([^:]+):(\S+)/) {
377                 push(@rv, { 'user' => $2, 'pass' => $3,
378                             'enabled' => !$1, 'line' => $lnum });
379                 }
380         $lnum++;
381         }
382 close(USERS);
383 if ($config{'sort_conf'}) {
384         return sort { $a->{'user'} cmp $b->{'user'} } @rv;
385         }
386 else {
387         return @rv;
388         }
389 }
390
391 # get_squid_user(&config)
392 # Returns the effective user and group (if any)
393 sub get_squid_user
394 {
395 if ($squid_version < 2) {
396         local $ceu = &find_config("cache_effective_user", $_[0]);
397         if ($ceu) { return ($ceu->{'values'}->[0], $ceu->{'values'}->[1]); }
398         return (undef, undef);
399         }
400 else {
401         local $ceu = &find_config("cache_effective_user", $_[0]);
402         local $ceg = &find_config("cache_effective_group", $_[0]);
403         return ($ceu->{'values'}->[0], $ceg ? $ceg->{'values'}->[0]
404                                             : $ceu->{'values'}->[1]);
405         }
406 }
407
408 # chown_files(user, group, config)
409 # Change ownership of all squid log and cache directories
410 sub chown_files
411 {
412 local(@list, $pidstruct, $pidfile);
413 @list = ( $config{'log_dir'} );
414
415 # add pidfile
416 if ($str = &find_config("pid_filename", $_[2])) {
417         $pidfile = $str->{'values'}->[0];
418         }
419 else { $pidfile = $config{'pid_file'}; }
420 push(@list, $pidfile);
421
422 # add other log directories
423 foreach $d ("cache_access_log", "access_log", "cache_log",
424             "cache_store_log", "cache_swap_log") {
425         if (($str = &find_config($d, $_[2])) &&
426             $str->{'values'}->[0] =~ /^(\S+)\/[^\/]+$/) {
427                 push(@list, $1);
428                 }
429         }
430
431 # add cache directories
432 if (@str = &find_config("cache_dir", $_[2])) {
433         foreach $str (@str) {
434                 push(@list, $str->{'values'}->[0]);
435                 }
436         }
437 else { push(@list, $config{'cache_dir'}); }
438 system("chown -Rf $_[0]:$_[1] ".join(" ",@list)." >/dev/null 2>&1");
439 }
440
441 # can_access(file)
442 sub can_access
443 {
444 local @f = grep { $_ ne '' } split(/\//, $_[0]);
445 return 1 if ($access{'root'} eq '/');
446 local @a = grep { $_ ne '' } split(/\//, $access{'root'});
447 local $i;
448 for($i=0; $i<@a; $i++) {
449         return 0 if ($a[$i] ne $f[$i]);
450         }
451 return 1;
452 }
453
454 # get_auth_file(&config)
455 sub get_auth_file
456 {
457 if ($squid_version >= 2.5) {
458         local @auth = &find_config("auth_param", $_[0]);
459         local ($program) = grep { $_->{'values'}->[0] eq 'basic' &&
460                                   $_->{'values'}->[1] eq 'program' } @auth;
461         return $program ? $program->{'values'}->[3] : undef;
462         }
463 else {
464         local $authprog = &find_value("authenticate_program", $_[0]);
465         return $authprog =~ /(\S+)\s+(\/\S+)$/ ? $2 : undef;
466         }
467 }
468
469 # parse_external(&external_acl_type)
470 sub parse_external
471 {
472 local @v = @{$_[0]->{'values'}};
473 local $rv = { 'name' => $v[0] };
474 for($i=1; $v[$i] =~ /^(\S+)=(\S+)$/; $i++) {
475         $rv->{'opts'}->{$1} = $2;
476         }
477 if ($v[$i] =~ /^\"(.*)\"$/) {
478         $rv->{'format'} = $1;
479         }
480 else {
481         $rv->{'format'} = $v[$i];
482         }
483 $i++;
484 $rv->{'program'} = $v[$i++];
485 $rv->{'args'} = [ @v[$i .. $#v] ];
486 return $rv;
487 }
488
489 # check_cache(&config, &caches)
490 # Returns 1 if the cache directory looks OK, 0 if not. Also fills in the 
491 # caches list
492 sub check_cache
493 {
494 local (@cachestruct, @caches, $c, $coss);
495 if (@cachestruct = &find_config("cache_dir", $_[0])) {
496         if ($squid_version >= 2.3) {
497                 @caches = map { $_->{'values'}->[1] } @cachestruct;
498                 }
499         else {
500                 @caches = map { $_->{'values'}->[0] } @cachestruct;
501                 }
502         ($coss) = grep { $_->{'values'}->[0] eq "coss" } @cachestruct;
503         }
504 else {
505         @caches = ( $config{'cache_dir'} );
506         }
507 @{$_[1]} = @caches;
508 if ($coss) {
509         # Allow COSS files too
510         foreach $c (@caches) {
511                 return 0 if (!-f $c && (!-d $c || !-d "$c/00"));
512                 }
513         }
514 else {
515         # Check for dirs only
516         foreach $c (@caches) {
517                 return 0 if (!-d $c || !-d "$c/00");
518                 }
519         }
520 return 1;
521 }
522
523 # get_squid_port()
524 # Returns the port Squid is listening on
525 sub get_squid_port
526 {
527 local $conf = &get_config();
528 local $port;
529 if ($squid_version >= 2.3) {
530         local ($p, $v);
531         LOOP: foreach $p (&find_config("http_port", $conf)) {
532                 foreach $v (@{$p->{'values'}}) {
533                         if ($v =~ /^(\d+)$/) {
534                                 $port = $1;
535                                 }
536                         elsif ($v =~ /^(\S+):(\d+)$/) {
537                                 $port = $2;
538                                 }
539                         last LOOP if ($port);
540                         }
541                 }
542         }
543 else {
544         $port = &find_value("http_port", $conf);
545         }
546 return defined($port) ? $port : 3128;
547 }
548
549 # apply_configuration()
550 # Activate the current Squid configuration
551 sub apply_configuration
552 {
553 if ($config{'squid_restart'}) {
554         local $out = &backquote_logged("$config{'squid_restart'} 2>&1");
555         return "<pre>$out</pre>" if ($?);
556         }
557 else {
558         $out = &backquote_logged("$config{'squid_path'} -f $config{'squid_conf'} -k reconfigure 2>&1");
559         return "<pre>$out</pre>" if ($? && $out !~ /warning/i);
560         }
561 return undef;
562 }
563
564 # list_cachemgr_actions()
565 # Returns a list of actions for use in the cachemgr_passwd directive
566 sub list_cachemgr_actions
567 {
568 return ("5min" ,"60min" ,"asndb" ,"authenticator" ,"cbdata" ,"client_list" ,"comm_incoming" ,"config" ,"counters" ,"delay" ,"digest_stats" ,"dns" ,"events" ,"filedescriptors" ,"fqdncache" ,"histograms" ,"http_headers" ,"info" ,"io" ,"ipcache" ,"mem" ,"menu" ,"netdb" ,"non_peers" ,"objects" ,"offline_toggle" ,"pconn" ,"peer_select" ,"redirector" ,"refresh" ,"server_list" ,"shutdown" ,"store_digest" ,"storedir" ,"utilization" ,"via_headers" ,"vm_objects");
569 }
570
571 1;
572