comparison cgi-bin/edit-whiteclnt.in @ 0:c7f6b056b673

First import of vendor version
author Peter Gervai <grin@grin.hu>
date Tue, 10 Mar 2009 13:49:58 +0100
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:c7f6b056b673
1 #! @PERL@ -wT
2
3 # display and edit a DCC whitelist file
4
5 # Copyright (c) 2008 by Rhyolite Software, LLC
6 #
7 # This agreement is not applicable to any entity which sells anti-spam
8 # solutions to others or provides an anti-spam solution as part of a
9 # security solution sold to other entities, or to a private network
10 # which employs the DCC or uses data provided by operation of the DCC
11 # but does not provide corresponding data to other users.
12 #
13 # Permission to use, copy, modify, and distribute this software without
14 # changes for any purpose with or without fee is hereby granted, provided
15 # that the above copyright notice and this permission notice appear in all
16 # copies and any distributed versions or copies are either unchanged
17 # or not called anything similar to "DCC" or "Distributed Checksum
18 # Clearinghouse".
19 #
20 # Parties not eligible to receive a license under this agreement can
21 # obtain a commercial license to use DCC by contacting Rhyolite Software
22 # at sales@rhyolite.com.
23 #
24 # A commercial license would be for Distributed Checksum and Reputation
25 # Clearinghouse software. That software includes additional features. This
26 # free license for Distributed ChecksumClearinghouse Software does not in any
27 # way grant permision to use Distributed Checksum and Reputation Clearinghouse
28 # software
29 #
30 # THE SOFTWARE IS PROVIDED "AS IS" AND RHYOLITE SOFTWARE, LLC DISCLAIMS ALL
31 # WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
32 # OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL RHYOLITE SOFTWARE, LLC
33 # BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
34 # OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
35 # WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
36 # ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
37 # SOFTWARE.
38 # Rhyolite Software DCC 1.3.103-1.58 $Revision$
39 # @configure_input@
40
41 # This file must protected with an equivalent to httpd.conf lines
42 # in the README file.
43
44 use strict 'subs';
45
46 my($main_whiteclnt); # path to the main whiteclnt file
47
48 my(@file); # list representation of the file
49 my(%dict); # dictionary of checksums and options
50 my(%def_options); # option settings from main whiteclnt file
51
52 my($have_entry_form, $form_marked,
53 $cur_pos, $cur_key, $cur_entry, $cur_index);
54
55 my $form_num = 0;
56
57
58 # get DCC parameters
59 local($whiteclnt, # path to the per-user whitelist file
60 %query,
61 $hostname,
62 $thold_cks, # checksums that can have thresholds
63 $user,
64 $edit_url,
65 $list_msg_link,
66 $edit_url, $edit_link,
67 $url_ques,
68 $sub_white, # 'subsitute' headers from dcc_conf
69 $form_hidden, # state for main form
70 $whiteclnt_version, #webuser version ...
71 $whiteclnt_notify, #webuser mail-notify=X mailbox=Y
72 $whiteclnt_notify_pat, #regex for #webuser mail-notify=X mailbox=Y
73 $whiteclnt_lock, #webuser (un)locked
74 $whiteclnt_change_log, #list of dates when file was changed
75 $whiteclnt_cur_key); #editing position in the file
76
77 do('@cgibin@/common') || die("could not get DCC configuration: $!\n");
78
79
80 # display the file literally
81 if ($query{literal}) {
82 my($buf);
83
84 open(WHITECLNT, "< $whiteclnt") or html_whine("open($whiteclnt): $!");
85
86 print "Content-type: text/plain\n";
87 print "Expires: Thu, 01 Dec 1994 16:00:00 GMT\n";
88 print "pragma: no-cache\n\n";
89 print $buf
90 while (read(WHITECLNT, $buf, 4*1024));
91 print "\n";
92
93 close(WHITECLNT);
94 exit;
95 }
96
97 # lock, read and parse the whiteclnt file
98 read_whiteclnt(\@file, \%dict);
99
100 # get option defaults from the main whiteclnt file
101 read_whitedefs(\%def_options);
102
103
104 # get current position for the entry editing form
105 $cur_pos = $query{pos};
106
107 # find a whitecnt file entry to edit
108 if ($query{key}) {
109 $cur_key = $query{key};
110 } elsif ($query{auto} && $query{type} && $query{val}) {
111 my @new_entry = ck_new_white_entry("", "ok", $query{type}, $query{val});
112 $cur_key = $new_entry[0] if (defined($new_entry[1]));
113 }
114 $cur_entry = $dict{$cur_key} if ($cur_key);
115
116
117 html_head("DCC Whitelist for $user at $hostname");
118 common_buttons();
119 print "<TR><TD colspan=10>return to $list_msg_link${url_ques}msg=$query{msg}\">logged message $query{msg}</A>\n"
120 if ($query{msg});
121 print <<EOF;
122 <TR><TD colspan=10>$edit_link${url_ques}literal=yes"
123 TARGET="DCC literal whiteclnt">Literal contents of whitelist</A>.
124 </TABLE>
125
126 EOF
127
128
129 # add new entry
130 if ($query{Add}) {
131 my(@new_entry, $msg, $prev, $cur, $add_pos);
132
133 @new_entry = ck_new_white_entry($query{comment}, $query{count},
134 $query{type}, $query{val});
135 give_up($new_entry[0]) if (!defined($new_entry[1]));
136
137 # insert into the file instead of appending to the end if we have
138 # a valid position
139 if (defined($cur_pos)) {
140 $add_pos = next_index($cur_pos);
141 } elsif ($cur_key) {
142 ($prev, $cur, $add_pos) = neighbors($cur_key);
143 }
144
145 $cur_key = $new_entry[0];
146 $cur_entry = \@new_entry;
147 give_up("entry already present") if ($dict{$cur_key});
148
149 # send the new entry to the disk with the rest of the file
150 $whiteclnt_cur_key = $cur_key;
151 $msg = chg_white_entry(\@file, \%dict, $cur_key, \@new_entry, $add_pos);
152 give_up($msg) if ($msg);
153
154 # re-prime the form with cleaned comment
155 $cur_entry = $dict{$cur_key};
156 give_up("new entry did not reach file") if (!$cur_entry);
157
158 finish("whitelist entry added");
159 }
160
161
162
163 # change current whitelist entry
164 if ($query{Change}) {
165 my(@new_entry, $msg);
166
167 give_up("no entry selected to change") if (!$cur_key);
168 give_up("entry '$cur_key' has disappeared")
169 if (!$cur_entry || !$$cur_entry[0]);
170
171 @new_entry = ck_new_white_entry($query{comment}, $query{count},
172 $query{type}, $query{val});
173 give_up($new_entry[0]) if (!defined($new_entry[1]));
174
175 give_up("no changes requested")
176 if ($$cur_entry[1] eq $new_entry[1]
177 && $$cur_entry[2] eq $new_entry[2]);
178
179 # send the change to the disk with the rest of the file
180 $whiteclnt_cur_key = $cur_key;
181 $msg = chg_white_entry(\@file, \%dict, $cur_key, \@new_entry);
182 give_up($msg) if ($msg);
183
184 # re-prime the form with cleaned comment
185 $cur_entry = $dict{$cur_key};
186 give_up("changed entry did not reach file") if (!$cur_entry);
187
188 finish("whitelist entry changed");
189 }
190
191
192
193 # delete current entry
194 if ($query{Delete}) {
195 my($prev, $cur, $next, $new_key, $msg);
196
197 give_up("no entry selected to delete") if (!$cur_key);
198 give_up("entry '$cur_key' has disappeared")
199 if (!$cur_entry || !$$cur_entry[0]);
200
201 # find a neighbor of the entry to be deleted
202 ($prev, $cur, $next) = neighbors($cur_key);
203 $new_key = ${$file[$prev]}[0] if (defined($prev));
204
205 # write everything to the new file except the deleted entry
206 $whiteclnt_cur_key = $cur_key;
207 $msg = chg_white_entry(\@file, \%dict, $cur_key, undef);
208 give_up($msg) if ($msg);
209
210 # keep the add/change/delete form in place if possible
211 $cur_key = $new_key;
212 undef($cur_entry);
213 delete $query{comment};
214 delete $query{count};
215 delete $query{type};
216 delete $query{val};
217 $cur_entry = $dict{$cur_key} if ($cur_key);
218
219 finish("whitelist entry deleted");
220 }
221
222
223 # move the current entry up
224 if ($query{Up}) {
225 up(1);
226 }
227
228 if ($query{Up5}) {
229 up(5);
230 }
231
232 sub up {
233 my($delta) = @_;
234 my($prev, $cur, $next, $moved, @new_file, $msg);
235
236 if ($cur_entry) {
237 # move an existing entry
238 while ($delta) {
239 --$delta;
240 # It is inefficient but easy and clearly correct to repeated
241 # search the array of entries for the target and then build
242 # a new array.
243 ($prev, $cur, $next) = neighbors($cur_key);
244 if (!$prev) {
245 give_up("cannot move above the top") if (!$moved);
246 last;
247 }
248
249 @new_file = (@file[0 .. $prev-1],
250 $file[$cur], $file[$prev],
251 @file[$cur+1 .. $#file]);
252 @file = @new_file;
253 $moved = 1;
254 }
255 $whiteclnt_cur_key = $cur_key;
256 $msg = write_whiteclnt(@file);
257 give_up($msg) if ($msg);
258 read_whiteclnt(\@file, \%dict);
259
260 } else {
261 # move a new or proposed entry
262 $cur_pos = prev_index($cur_pos, $delta);
263 }
264 print_form_file();
265 }
266
267
268 # move the current entry down
269 if ($query{Down}) {
270 down(1);
271 }
272
273 if ($query{Down5}) {
274 down(5);
275 }
276
277 sub down {
278 my($delta) = @_;
279 my($prev, $cur, $next, @new_file, $msg);
280
281 if ($cur_entry) {
282 # move an existing entry
283 while ($delta) {
284 --$delta;
285 ($prev, $cur, $next) = neighbors($cur_key);
286 if (!$next) {
287 give_up("cannot move below the bottom") if (!$moved);
288 last;
289 }
290
291 @new_file = (@file[0 .. $cur-1],
292 $file[$next], $file[$cur],
293 @file[$next+1 .. $#file]);
294 @file = @new_file;
295 $moved = 1;
296 }
297 $whiteclnt_cur_key = $cur_key;
298 $msg = write_whiteclnt(@file);
299 give_up($msg) if ($msg);
300 read_whiteclnt(\@file, \%dict);
301
302 } elsif (defined($cur_pos)) {
303 # move position of a future entry
304 $cur_pos = next_index($cur_pos, $delta);
305 }
306 print_form_file();
307 }
308
309
310 # undo the previous change
311 if ($query{Undo}) {
312 my $msg = undo_whiteclnt();
313 give_up($msg) if ($msg);
314
315 $cur_key = $whiteclnt_cur_key;
316 read_whiteclnt(\@file, \%dict);
317 $cur_key = $whiteclnt_cur_key if ($whiteclnt_cur_key);
318
319 # put the add/change/delete form back in place
320 if ($cur_key) {
321 $cur_entry = $dict{$cur_key};
322 } else {
323 undef($cur_entry);
324 delete $query{comment};
325 delete $query{count};
326 delete $query{type};
327 delete $query{val};
328 }
329
330 finish("change undone");
331 }
332
333
334 # change new log file mail notifcations
335 my $old_notify = $whiteclnt_notify;
336 if ($query{notify}) {
337 if ($query{notify} =~ /off/) {
338 $whiteclnt_notify =~ s/$whiteclnt_notify_pat$/${1}off$3$4/i;
339 } elsif ($query{notify} =~ /on/) {
340 $whiteclnt_notify =~ s/$whiteclnt_notify_pat$/${1}on$3$4/i;
341 }
342 }
343 if (defined($query{notifybox})) {
344 my $new_box = $query{notifybox};
345 $new_box =~ s/^\s+(.*)\s*$/$1/;
346 $whiteclnt_notify =~ s/$whiteclnt_notify_pat$/$1$2$3$new_box/i;
347
348 give_up('The notification mailbox is limited to -, _, letters, and digits')
349 if ($whiteclnt_notify !~ /^$whiteclnt_notify_pat$/);
350 }
351 if ($whiteclnt_notify ne $old_notify) {
352 $whiteclnt_cur_key = "";
353 my $msg = write_whiteclnt(@file);
354 give_up($msg) if ($msg);
355 read_whiteclnt(\@file, \%dict);
356 }
357
358 # process requests to change options
359 option_form("dccenable", "On", "dcc-on", "Off", "dcc-off");
360 option_form("greyfilter", "On", "greylist-on", "Off", "greylist-off");;
361 option_form("greylog", "On", "greylist-log-on", "Off", "greylist-log-off");
362 option_form("mtafirst", "first", "MTA-first", "last", "MTA-last");
363 option_form("rep", "On", "DCC-rep-on", "Off", "DCC-rep-off");
364 option_form("dnsbl1", "On", "dnsbl1-on", "Off", "dnsbl1-off");
365 option_form("dnsbl2", "On", "dnsbl2-on", "Off", "dnsbl2-off");
366 option_form("dnsbl3", "On", "dnsbl3-on", "Off", "dnsbl3-off");
367 option_form("logall", "On", "log-all", "Off", "log-normal");
368 option_form("logsubdir", "day", "log-subdirectory-day",
369 "hour", "log-subdirectory-hour",
370 "minute", "log-subdirectory-minute");
371 option_form("discardok", "discard spam", "forced-discard-ok",
372 "delay mail", "no-forced-discard");
373
374 # process requests from the HTTP client to change the threshold
375 foreach my $ck (split(/,/, $thold_cks)) {
376 my $nm = "thold-$ck";
377 foreach my $val ($query{$nm}, $query{"text-$nm"}) {
378 next if (!$val);
379 if ($val =~ /^Default/) {
380 set_option($nm);
381 } elsif (!parse_thold_value($ck, $val)) {
382 give_up("invalid threshold setting $nm='$val'");
383 } else {
384 set_option($nm, "option threshold $ck,$val\n");
385 }
386 last;
387 }
388 }
389
390 # nothing to do?
391 give_up("entry '$cur_key' has disappeared")
392 if (!$query{auto} && $cur_key && (!$cur_entry || !$$cur_entry[0]));
393 print_form_file($query{result}
394 ? "<P class=warn>$query{result}</STRONG>\n"
395 : "");
396
397
398
399
400 #############################################################################
401
402 # display the whiteclnt file with the option setting form and and quit
403 sub print_form_file {
404 my($result) = @_; # "" or some kind of error message
405
406 close(WHITECLNT);
407
408 my $locked = ($whiteclnt_lock =~ /\blocked/) ? " disabled" : "";
409
410 # display any error message from the previous action
411 print $result ? $result : "<P>&nbsp;\n";
412
413 # generate table of forms to control option lines
414 print "<P>\n<TABLE border=0>\n";
415
416 print_form_start("<TR><TD>", "", "");
417 print "\t";
418 undo_form($locked);
419 print "\t</FORM>\n";
420
421 # two HTML forms for the '#webuser...' line
422 $whiteclnt_notify =~ /$whiteclnt_notify_pat/;
423 my $notify_cur = $2;
424 my $notifybox = $4;
425 my $notify_on_locked = ($notify_cur eq "on") ? " disabled" : $locked;
426 my $notify_off_locked = ($notify_cur eq "off") ? " disabled" : $locked;
427 print_form_start("<TR><TD class=first>", "", "");
428 print <<EOF;
429 mail notifications to
430 <INPUT $notify_off_locked type=text name=notifybox value='$notifybox' size=12>
431 <STRONG>$notify_cur</STRONG>
432 </FORM>
433 EOF
434 print_form_start(" <TD>", "", "");
435 print_button("\t", "notify", $notify_on_locked, "on");
436 print_button("\t", "notify", $notify_off_locked, "off");
437 print "\t</FORM>\n";
438
439 table_row_form("dccenable", "DCC", $locked, "dcc-off", "dcc-on");
440 if ($DCCM_ARGS =~ /-G/ || $DCCIFD_ARGS =~ /-G/
441 || (defined($GREY_CLIENT_ARGS) && $GREY_CLIENT_ARGS ne "")) {
442 table_row_form("greyfilter", "greylist filter", $locked,
443 "greylist-off", "greylist-on");
444 table_row_form("greylog", "greylist log", $locked,
445 "greylist-log-off", "greylist-log-on");
446 }
447 table_row_form("mtafirst", "check MTA blacklist", $locked,
448 "MTA-last", "MTA-first", "last", "first");
449 # ask about DNSBLs if they are available
450 my $args = "";
451 $args = "$DNSBL_ARGS" if (defined($DNSBL_ARGS));
452 $args .= " $DCCM_ARGS" if (defined($DCCM_ARGS));
453 $args .= " $DCCIFD_ARGS" if (defined($DCCIFD_ARGS));
454 if ($args =~ /-B/) {
455 if ($args !~ /-B\s*set:group=\d+/i) {
456 # only one question if there are no groups
457 table_row_form("dnsbl1", "DNS blacklist checking", $locked);
458 } else {
459 table_row_form("dnsbl1", "DNS blacklist #1 checking", $locked);
460 table_row_form("dnsbl2", "DNS blacklist #2 checking", $locked);
461 table_row_form("dnsbl3", "DNS blacklist #3 checking", $locked);
462 }
463 }
464 table_row_form("logall", "debug logging", $locked,
465 "log-normal", "log-all");
466 table_row_form("discardok",
467 "<STRONG></STRONG> also addressed to others",
468 $locked, "no-forced-discard", "forced-discard-ok",
469 "delay mail", "discard spam");
470
471 # forms for checksum thresholds
472 foreach my $ck (split(/,/, $thold_cks)) {
473 my($cur_val, $sw_val, $nm, $def_label, $bydef,
474 $dis_field, $dis_def, $dis_never);
475
476 $nm = "thold-" . $ck;
477 # construct label for the default button from default value
478 $def_label = $def_options{$nm};
479 $def_label =~ s/.*<STRONG>([^<]+)<.*/Default ($1)/;
480 if (defined($dict{$nm})) {
481 $cur_val = $dict{$nm}[2];
482 $cur_val =~ s/.*,([-_a-z0-9%]+)\s+$/$1/i;
483 $bydef = '';
484 $sw_val = $cur_val;
485 } else {
486 $cur_val = $def_options{$nm};
487 $cur_val =~ s@<STRONG>(.*)</STRONG>(.*)@$1@;
488 $bydef = $2;
489 $sw_val = 'Default';
490 }
491 $dis_field = $locked;
492 $dis_def = $locked;
493 $dis_never = $locked;
494 $dis_def = " class=selected disabled" if ($sw_val eq "Default");
495 $dis_never = " class=selected disabled" if ($sw_val eq "Never");
496 # changing reputation thresholds ought to affect tagging
497 # even if reputation checking is turned off
498
499 print_form_start("<TR><TD class=first>", "", "");
500 print <<EOF;
501 <EM>$ck</EM> threshold$bydef
502 <INPUT type=text$dis_field name='text-$nm' value='$cur_val' size=5>
503 </FORM>
504 EOF
505 print_form_start(" <TD>", "", "");
506 print_button("\t", $nm, $dis_def, $def_label);
507 print_button("\t", $nm, $dis_never, "Never");
508 # "many" makes no sense for either reputation threshold
509 print_button("\t", $nm,
510 $sw_val eq "many" ? " disabled" : $locked,
511 "many")
512 if ($ck !~ /^rep/i);
513 print "\t</FORM>\n";
514 }
515
516 print "</TABLE>\n\n<P>\n<HR>\n";
517
518 # display a form for a new entry before the file if we have not
519 # been given a position or an entry to modify
520 print_entry_form($locked) if (!$cur_key && !defined($cur_pos));
521
522 print_whiteclnt_file($result, $locked);
523 }
524
525
526
527 # display the common start of forms
528 sub print_form_start {
529 my($before, # HTML before start of form
530 $tag, # tag on action
531 $after # HTML after start of form
532 ) = @_;
533
534 print $before if ($before);
535 print "<FORM class=nopad ACTION='$edit_url";
536 print $tag if ($tag);
537 print "' method=POST>$form_hidden\n";
538 print $after if ($after);
539 print "\t<INPUT type=hidden name=msg value='$query{msg}'>\n"
540 if ($query{msg});
541 if ($cur_key) {
542 print "\t<INPUT type=hidden name=key value='";
543 print html_str_encode($cur_key);
544 print "'>\n";
545 }
546 if ($cur_pos) {
547 print "\t<INPUT type=hidden name=pos value='";
548 print html_str_encode($cur_pos);
549 print "'>\n";
550 }
551 }
552
553
554
555 sub undo_form {
556 my($locked) = @_;
557
558 print "<INPUT class=small";
559 print newest_whiteclnt_bak() ? $locked : " disabled";
560 print " type=submit name='Undo' value='Undo Previous Change'>\n";
561 }
562
563
564
565 # display the entry editing form
566 sub print_entry_form {
567 my($locked, $result) = @_;
568 my($add_str, $new_val, $comment, $change_ok, $prev, $cur, $next);
569
570 return if ($have_entry_form);
571 $have_entry_form = 1;
572
573 # prime the form with the currently selected whiteclnt entry, if any
574 if ($cur_entry) {
575 $comment = $$cur_entry[1];
576 $query{comment} = html_str_encode($comment);
577
578 my $value = $$cur_entry[2];
579 $value =~ s/(\S+)\s+//;
580 $query{count} = $1;
581 ($query{type}, $query{val}) = parse_type_value($value);
582 $change_ok = $locked;
583 } else {
584 # "disabled" does not work with Netscape 4.*, but we have to handle
585 # changes without a valid key, so don't worry about it
586 $change_ok = " disabled";
587 }
588
589 # compute a comment if this came from a log file
590 if ($query{auto} && !$cur_entry) {
591 $comment = " \n#";
592 $comment .= " added from logged message $query{msg}"
593 if ($query{msg});
594 $comment .= strftime(" %x", localtime);
595 $comment = html_str_encode($comment);
596 $query{count} = "OK";
597 } else {
598 $comment = $query{comment};
599 if (!$comment) {
600 $comment = "";
601 } else {
602 $comment =~ s/\s+$//mg;
603 # need a blank on a leading blank line to preserve it in Mozilla
604 $comment =~ s/^\n/ \n/;
605 }
606 }
607
608 if (!$form_marked) {
609 print "<A NAME='cur_key'></A>";
610 $form_marked = 1;
611 }
612 print_form_start("", "#cur_key", "<TABLE border=0>\n<TR><TD>&nbsp;");
613 print " <TD>";
614 undo_form($locked);
615 print_button("\t", "Add", $locked, "Add");
616 if (defined($cur_pos)) {
617 $prev = prev_index($cur_pos);
618 $next = next_index($cur_pos);
619 } elsif ($cur_key) {
620 ($prev, $cur, $next) = neighbors($cur_key);
621 } else {
622 undef($prev);
623 undef($next);
624 }
625 print_button("\t", "Up", !defined($prev) ? " disabled" : $locked, "Up");
626 print_button("\t", "Up5", !defined($prev) ? " disabled" : $locked, "Up 5");
627 print_button("\t", "Down", !$next ? " disabled" : $locked, "Down");
628 print_button("\t", "Down5", !$next ? " disabled" : $locked, "Down 5");
629 if ($query{auto} && !$cur_entry) {
630 print "\tfrom $list_msg_link${url_ques}msg=$query{msg}\">logged message $query{msg}</A>\n";
631 } else {
632 print_button("\t", "Change", $change_ok, "Change");
633 print_button("\t", "Delete", $change_ok, "Delete");
634 print "whitelist entry\n";
635 }
636 print <<EOF;
637 <TR><TD>Description
638 <TD><TEXTAREA$locked name=comment rows=3 cols=70>$comment</TEXTAREA>
639 <TR><TD>&nbsp;
640 <TD><SELECT class=small$locked name=count>
641 EOF
642 $query{count} = "OK" if (!$query{count});
643 print_option("count", "OK");
644 print_option("count", "OK2");
645 print_option("count", "many");
646 print "\t</SELECT>\n";
647
648 print "\t<SELECT class=small$locked name=type>\n";
649 $query{type} = "env_From" if (!$query{type});
650 print_option("type", "env_From");
651 print_option("type", "env_To");
652 print_option("type", "From");
653 print_option("type", "IP");
654 print_option("type", "Message-ID");
655 # allow selection of checksums specified with -S in /var/dcc/dcc_conf
656 foreach my $hdr (split(/[|)(]+/, $sub_white)) {
657 my($label);
658 $hdr =~ s/\\s\+/ /;
659 next if ($hdr =~ /^s*$/);
660 $label = $hdr;
661 $label =~ s/^substitute\s+//i;
662 print_option("type", $label, $hdr);
663 }
664 print_option("type", "Hex Body");
665 print_option("type", "Hex Fuz1");
666 print_option("type", "Hex Fuz2");
667 print "\t</SELECT>\n";
668
669 print "\t<INPUT type=text name=val size=40";
670 if ($query{val}) {
671 print " value='";
672 print html_str_encode($query{val});
673 print "'";
674 }
675 print ">\n";
676
677 print "<TR><TD colspan=10>return to $list_msg_link${url_ques}msg=$query{msg}\">logged message $query{msg}</A>\n"
678 if ($query{msg});
679
680 print "<TR><TD colspan=10>\n";
681 print $result ? $result : "&nbsp;";
682 print "</TABLE>\n</FORM>\n\n";
683 }
684
685
686
687 # find indeces of previous, current, and next entries
688 # return a list of 3 entries of the preceding, current, and following indeces
689 sub neighbors {
690 my($tgt_key) = @_;
691 my($prev, $cur, $index, $entry);
692
693 # look for the current entry while tracking predecessors
694 $index = 0;
695 foreach $entry (@file) {
696 next if (!ref($entry));
697
698 # ignore deleted lines, options, and include lines
699 next if (!$$entry[0] || !defined($$entry[1])
700 || $$entry[2] =~ /^option/);
701
702 # stop at the first entry when there is no current position
703 return ($prev, $cur, $index) if (!$tgt_key);
704
705 if ($$entry[0] eq $tgt_key) {
706 $cur = $index;
707 last;
708 }
709 $prev = $index;
710 } continue { ++$index; }
711
712 do {
713 return ($prev, $cur, undef) if ($index >= $#file);
714 $entry = $file[++$index];
715 } while (!$$entry[0] || !defined($$entry[1]) || $$entry[2] =~ /^option/);
716 return ($prev, $cur, $index);
717 }
718
719
720
721 sub prev_index {
722 my($pos, $delta) = @_;
723 my ($entry);
724
725 $pos = $#file if (!$pos);
726
727 while (--$pos >= 0) {
728 $entry = $file[$pos];
729 # skip deleted entries
730 return $pos
731 if ($$entry[0] && defined($$entry[1]) && $$entry[2] !~ /^option/
732 && (!$delta || !--$delta));
733 }
734 return undef;
735 }
736
737
738
739 sub next_index {
740 my($pos, $delta) = @_;
741 my ($entry);
742
743 $pos = $#file if (!$pos);
744
745 while (++$pos <= $#file) {
746 $entry = $file[$pos];
747 # skip deleted entries
748 return $pos
749 if ($$entry[0] && defined($$entry[1]) && $$entry[2] !~ /^option/
750 && (!$delta || !--$delta));
751 }
752 return undef;
753 }
754
755
756
757 sub set_option {
758 my($key, $line) = @_;
759 my($msg);
760
761 # put the new value, if any, into the spare slot created when the file
762 # was read into memory
763 $file[1] = ["", "", $line] if ($line);
764
765 # delete the old value if any
766 $whiteclnt_cur_key = "";
767
768 $msg = chg_white_entry(\@file, \%dict, $key);
769 give_up($msg) if ($msg);
770 }
771
772
773
774 # see if an form for an option was selected and process the result if so
775 # The first arg is the name of the option. It is followed by
776 # (form-value,file-value) pairs
777 sub option_form {
778 my($key, $new_formval, $formval, $fileval);
779
780 $key = shift @_;
781 $new_formval = $query{$key};
782 return if (!$new_formval);
783
784 if ($new_formval =~ /^Default/) {
785 set_option("$key");
786 return;
787 }
788 while ($#_ > 0) {
789 $formval = shift @_;
790 $fileval = shift @_;
791 if ($new_formval eq $formval) {
792 set_option("$key", "option $fileval\n");
793 return;
794 }
795 }
796 give_up("invalid setting $key='$new_formval'");
797 }
798
799
800
801 sub finish {
802 print_form_file("<P><STRONG>" . html_str_encode($_[0]) . "</STRONG>\n");
803 }
804
805
806
807 sub give_up {
808 print_form_file("<P class=warn><STRONG>"
809 . html_str_encode($_[0]) . "</STRONG>\n");
810 }
811
812
813
814 # You cannot use real HTML 4 buttons because Microsoft gets them all wrong.
815 # Contrary to the standard, they return all type=submit buttons.
816 # They also return any text label instead of the value, thereby removing
817 # most or all reason to use <BUTTON> instead of <INPUT>.
818 sub print_button {
819 my($lead, # HTML text before the control
820 $nm, # control name
821 $lock, # "" or " disabled"
822 $val) = @_; # value when selected
823
824 $lock = " class=small$lock" if ($lock !~ /class=/i);
825 print $lead;
826 print "<INPUT $lock type=submit name='$nm' value='$val'>\n";
827 }
828
829
830
831 # one line of the table of forms
832 sub table_row_form {
833 my($nm, # name of the option
834 $label, # label; <STRONG></STRONG> gets current
835 $locked, # "" or "disabled" when file read-only
836 $off, $on, # replace "off" and "on" in the file
837 $off_label, $on_label, # "off" & "on" for user
838 ) = @_;
839 my($button_cur, $dis_on, $dis_off, $dis_def, $label_cur,
840 $val_cur, $bydef);
841
842
843 $off = "$nm-off" if (!$off);
844 $on = "$nm-on" if (!$on);
845 $dis_on = $locked;
846 $dis_off = $locked;
847 $dis_def = $locked;
848 $button_cur = $locked ? $locked : " class=selected disabled";
849 if ($dict{$nm}
850 && $dict{$nm}[2] eq "option $on\n") {
851 $label_cur = $on_label ? $on_label : "<STRONG>on</STRONG>";
852 $val_cur = $label_cur;
853 $bydef = "";
854 $dis_on = $button_cur;
855 } elsif ($dict{$nm}
856 && $dict{$nm}[2] eq "option $off\n") {
857 $label_cur = $off_label ? $off_label : "<STRONG>off</STRONG>";
858 $val_cur = $label_cur;
859 $bydef = "";
860 $dis_off = $button_cur;
861 } else {
862 $label_cur = $def_options{$nm};
863 $val_cur = $label_cur;
864 $val_cur =~ s@(<STRONG>.*</STRONG>)(.*)@$1@;
865 $bydef = $2;
866 $dis_def = $button_cur;
867 }
868 # construct labels for "on" and "off" buttons
869 if ($on_label) {
870 $on_label =~ s/.*<STRONG>([^<]+)<.*/$1/;
871 } else {
872 $on_label = "On";
873 }
874 if ($off_label) {
875 $off_label =~ s/.*<STRONG>([^<]+)<.*/$1/;
876 } else {
877 $off_label = "Off";
878 }
879 # construct label for the default button from default value
880 $def_label = $def_options{$nm};
881 $def_label =~ s/.*<STRONG>([^<]+)<.*/Default ($1)/;
882 # construct label for the group of buttons
883 # use it as a pattern if the provided label contains "<STRONG></STRONG>",
884 if ($label !~ s@<STRONG></STRONG>(.*)@<STRONG>$val_cur</STRONG>$1$bydef@) {
885 $label .= " $label_cur";
886 }
887
888 print "<TR><TD class=first>$label\n";
889 print_form_start(" <TD>", "", "");
890 print_button("\t", $nm, $dis_def, $def_label);
891 print_button("\t", $nm, $dis_on, $on_label);
892 print_button("\t", $nm, $dis_off, $off_label);
893 print "\t</FORM>\n";
894 }
895
896
897
898 sub print_str {
899 my($lineno, $leader, $str) = @_;
900
901 while ($str =~ s/(.*\n?)// && $1) {
902 my $line = $1;
903 if ($line =~ /\n/) {
904 ++$lineno;
905 } else {
906 $line .= "\n";
907 $leader .= "? ";
908 }
909 print $lineno if ($query{debug});
910 print $leader;
911 print $line;
912 }
913 return $lineno;
914 }
915
916
917
918 sub print_option {
919 my($field, $label, $value) = @_;
920 my($s);
921
922 $s = "";
923 if ($query{$field}) {
924 if ($value && $query{$field} =~ /^$value$/i) {
925 $s = " selected"
926 } elsif ($query{$field} =~ /^$label$/i) {
927 $s = " selected";
928 }
929 }
930 if ($value) {
931 $value = " value=\"$value\"";
932 } else {
933 $value = "";
934 }
935 print "\t <OPTION class=small$s$value>$label</OPTION>\n";
936 }
937
938
939
940 # display the current contents of the whiteclnt file
941 # It is represented as an array or list of references to 3-tuples.
942 # The first of the three is the whitelist entry in a canonical form
943 # as a key uniquely identifying the entry.
944 # The second is a comment string of zero or more comment lines.
945 # The third is the DCC whiteclnt entry.
946 #
947 # The canonical form and the whiteclnt line of the first 3-tuple for a file
948 # are null, because it contains the comments, if any, before the file's
949 # preamble of dans when the file has been changed and flags.
950 # The file[1] is an empty slot for adding option settings.
951 # The last triple in a file may also lack a whitelist entry.
952
953 sub print_whiteclnt_file {
954 my($result, $locked) = @_;
955 my($preamble, $str, $url, $entry, $lineno, $in_pre, $leader, $end_select,
956 $tgt_key, $prev_key);
957
958 $url = $edit_link . $url_ques;
959 $url .= "msg=" . $query{msg} . "&amp;" if ($query{msg});
960 $url .= "key=";
961
962 $tgt_key = defined($cur_pos) ? ${$file[$cur_pos]}[0] : $cur_key;
963
964 # try to find an entry before the current entry to start the display
965 # in the browser's window
966 if ($tgt_key) {
967 my @prev_keys;
968
969 foreach $entry (@file) {
970 # ignore deleted lines, options, and include lines
971 next if (!$$entry[0] || !defined($$entry[1])
972 || $$entry[2] =~ /^option/);
973 shift(@prev_keys) if ($#prev_keys >= 2);
974 push(@prev_keys, $$entry[0]);
975 last if ($$entry[0] eq $tgt_key);
976 }
977 $prev_key = shift(@prev_keys);
978 }
979
980 $lineno = 1;
981 foreach $entry (@file) {
982 # do not list deleted entries
983 next if (!defined($$entry[1]));
984
985 # no options if not debugging
986 next if ($$entry[2] =~ /^option/ && !$query{debug});
987
988 # tell the browser that the form will be soon
989 if ($prev_key && $$entry[0] && $$entry[0] eq $prev_key) {
990 print "<A NAME='cur_key'></A>";
991 $form_marked = 1;
992 }
993
994 # mark the currently selected entry
995 if ($tgt_key && $$entry[0] && $$entry[0] eq $tgt_key) {
996 print "<STRONG>";
997 $leader = " &brvbar;\t";
998 $end_select = 1;
999 } else {
1000 $leader = "\t";
1001 $end_select = undef;
1002 }
1003
1004 if ($query{debug}) {
1005 if ($in_pre) {
1006 $in_pre = undef;
1007 print "</PRE>";
1008 }
1009 print "<HR>" if ($query{debug});
1010 }
1011 if (!$in_pre) {
1012 $in_pre = 1;
1013 print "<PRE class=nopad>";
1014 }
1015
1016 # display comment lines
1017 $str = $$entry[1];
1018 if (!$preamble) {
1019 # Display the preamble parameters after comments in first triple
1020 # but before the ultimate blank line in the comments, if present.
1021 $preamble = $whiteclnt_version;
1022 $preamble .= $whiteclnt_notify;
1023 $preamble .= $whiteclnt_lock;
1024 $preamble .= "#webuser cur_key $whiteclnt_cur_key\n"
1025 if ($whiteclnt_cur_key);
1026 $preamble .= $whiteclnt_change_log;
1027 if ($query{debug}) {
1028 $str .= $preamble if ($str !~ s/(\n?)\n$/\n$preamble$1/);
1029 }
1030 }
1031 $lineno = print_str($lineno, $leader, html_str_encode($str));
1032
1033 $str = $$entry[2];
1034 if ($$entry[0] && $$entry[2] !~ /^option/) {
1035 # Display an ordinary entry as a link for editing.
1036 chomp($str);
1037 # Suppress "substitute" noise
1038 $str =~ s/^(\S*\s+)substitute\s+/$1/;
1039 # use tab for blanks between the type and value
1040 $str =~ s/^(\S+)\s+(\S+)\s+/$1\t$2\t/;
1041 # make columns
1042 $str =~ s/^(\S+\s+\S{1,7})\t/$1\t\t/;
1043 $str = $url . url_encode($$entry[0]) . "#cur_key\">"
1044 . html_str_encode($str) . "</A>\n";
1045 } else {
1046 # just display option lines
1047 $str = html_str_encode($str);
1048 }
1049 $lineno = print_str($lineno, $leader, $str);
1050
1051 # put the editing form after the selected entry
1052 if ($end_select) {
1053 print "</STRONG></PRE>\n";
1054 $in_pre = undef;
1055 print_entry_form($locked, $result);
1056 }
1057 }
1058 print "</PRE>\n" if ($in_pre);
1059
1060 print_entry_form($locked, $result) if (!$have_entry_form);
1061
1062 close(WHITECLNT);
1063
1064 html_footer();
1065 print "</BODY>\n</HTML>\n";
1066
1067 exit;
1068 }