2 # This is a module with functions for HTML output.
4 # I separate it from the main STUMP stuff because these functions are
5 # bulky and not very interesting.
12 my $title = pop( @_ );
14 "Content-Type: text/html\n\n
20 print "<B> You are operating in demonstration mode. User actions will have no effect.</B><HR>\n";
26 print "\n<HR>Thank you for using <A HREF=$STUMP_URL>STUMP Robomoderator</A>.
28 Click <A HREF=$base_address>here</A> to return to WebSTUMP. -->
32 # prints a link to help
33 # accepts topic id and topic name.
36 my $topic_name = pop( @_ );
37 my $topic = pop( @_ );
39 #&print_image( "help.gif", "" );
41 print "<A HREF=$base_address?action=help&topic=$topic TARGET=new>Click here for help on $topic_name</A>\n";
45 # prints image and an alt text
47 sub print_image { # image_file, alt_text
51 print "<IMG SRC=$base_address_for_files/images/$file ALT=\"$alt\" ALIGN=BOTTOMP>\n";
54 # prints the welcome page and login screen.
55 sub html_welcome_page {
56 &begin_html( "Welcome to WebSTUMP" );
60 "Welcome to WebSTUMP, the moderators' front end for <A
61 HREF=http://www.algebra.com/~ichudov/stump>STUMP</A> users -- USENET newsgroup
62 moderators. Only authorized users are allowed to log into this
67 my $motd_file = "$webstump_home/config/motd";
69 if( -f $motd_file && -r $motd_file ){
70 open( MOTD, $motd_file );
71 print "<B>Message of the Day:</B><BR><PRE>\n";
72 print while( <MOTD> );
78 Newsgroups Status:<BR>
81 for( sort @newsgroups_array ) {
84 my $count = &get_article_count( $_ );
86 print " <A HREF=$base_address?action=login_screen\&newsgroup=$_>$_</A>";
87 &print_image( "smiley.gif", "" ) if $count;
91 print "<TD>$count messages in queue<BR></TD>";
92 # print "<TD><A HREF=$base_address?action=init_request_newsgroup_creation\&newsgroup=$_>Request creation</A></TD>\n";
96 print "<HR>Note: click on the newsgroup to login in as moderator.
97 <!-- Click on 'Request Creation' to ask a sysadmin at a specific domain
98 to carry your newsgroup. -->\n<HR>
99 <A HREF=$base_address?action=admin_login>Click here to administer this WebSTUMP installation</A>
104 # prints the login screen for newsgroup.
105 sub html_login_screen {
106 my $newsgroup = $request{'newsgroup'} || &error( "newsgroup not defined" );
108 my $count = &get_article_count( $newsgroup );
112 &begin_html( "$count articles in queue for $newsgroup" );
114 &begin_html( "Empty Queue for $newsgroup" );
118 " Welcome to the Moderation Center for $newsgroup. Please bookmark
122 my $color = "", $end_color = "";
125 $color = "<font color=red>";
126 $end_color = "<font color=black>";
130 "<FORM METHOD=$request_method action=$base_address>
131 <INPUT NAME=action VALUE=moderation_screen TYPE=hidden>
134 &print_image( "new_tiny2.gif", "new" ) if $count;
136 print " articles available)<BR> $end_color
137 Login: <INPUT NAME=moderator VALUE=\"\" SIZE=20>
139 Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
141 <INPUT TYPE=submit VALUE=\"Proceed with Login\">
142 <INPUT TYPE=reset VALUE=\"Reset\">
143 <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
145 Please log into $newsgroup. You can only log in if you know your login id
146 and know the secret password. You should not give your password to any
147 unauthorized user. Your login id and password are NOT case sentitive,
149 for example, \"xyzzy\" and \"XyZZY\" are equally valid.<P>
153 Log in as \"admin\" if you want to
155 <LI> edit filtering lists.";
157 &link_to_help( "filter-lists", "Filter Lists" );
160 <LI> add/delete users or change their passwords.
161 <LI> First Time Users: You have to log in as admin and add a moderator user
162 who will be able to moderate the newsgroup. Then log in again as that
163 user. If you are a new user, you have to have your admin password assigned to
164 you by the administrator.
171 # prints the login screen for newsgroup.
172 sub admin_login_screen {
173 &begin_html( "Administrative login" );
177 Attention: this page is only for the maintainer of the whole WebSTUMP
178 installation. Please return to the main page if you are not the maintainer
179 of this installation. <HR>
183 "<FORM METHOD=$request_method action=$base_address>
184 <INPUT NAME=action VALUE=webstump_admin_screen TYPE=hidden>
185 Password: <INPUT NAME=password TYPE=password VALUE=\"\" SIZE=20>
187 <INPUT TYPE=submit VALUE=\"Proceed with Login\">
188 <INPUT TYPE=reset VALUE=\"Reset\">
195 # main moderation page -- old version
196 sub html_moderate_article {
197 my $newsgroup = &required_parameter( 'newsgroup' );
198 my $moderator = $request{'moderator'};
199 my $password = $request{'password'};
200 my $file = shift @_ || &required_parameter('file');
202 &begin_html( "Main Moderation Screen: $newsgroup" );
205 &read_rejection_reasons;
207 my $dir = "$queues_dir/$newsgroup";
209 if( -d "$dir/$file" && open( TEXT_FILES, "$dir/$file/text.files.lst" ) ) {
211 print "<HR>\n" if &print_article_warning( $file );
216 while( $filename = <TEXT_FILES> ) {
217 open( ARTICLE, "$dir/$file/$filename" );
219 $embolden= m/^(?:from|subject)\s*\:/i;
223 $_= "<strong>$_</strong>" if $embolden;
225 $inhead= 0 unless m/\S/;
231 print "\n</PRE>\n\n";
233 &print_images( $newsgroup, "$dir/$file", $file);
236 print "This message ($dir/$file) no longer exists -- maybe it was " .
237 "approved or rejected by another moderator.";
241 <FORM NAME=decision METHOD=$request_method action=$base_address>
245 <INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
246 &html_print_credentials;
247 print "<SELECT NAME=\"decision_$file\">
248 <OPTION VALUE=\"approve\">Approve</OPTION>
249 <OPTION VALUE=\"leave\">Put to back of queue</OPTION>
250 <OPTION VALUE=\"consider\">Back of queue, adding mark requesting further consideration</OPTION>
253 foreach (sort(keys %rejection_reasons)) {
254 print "<OPTION VALUE=\"reject $_\">Reject -- $rejection_reasons{$_}</OPTION>\n";
259 print "</SELECT><BR> Comment: <INPUT NAME=comment VALUE=\"\" SIZE=80><BR>";
262 <INPUT TYPE=radio NAME=poster_decision VALUE=nothing CHECKED>Don't change poster's status</INPUT>
263 <INPUT TYPE=radio NAME=poster_decision VALUE=preapprove
264 >Preapprove poster</INPUT>
265 <INPUT TYPE=radio NAME=poster_decision VALUE=ban
266 ONCLICK=\"alert( 'Banning a poster is a controversial practice'); \"
267 > Ban All Posts by this Person (Careful!)</INPUT>
269 <INPUT TYPE=radio NAME=thread_decision VALUE=nothing CHECKED>Don't change thread's status</INPUT>
270 <!-- <INPUT TYPE=radio NAME=thread_decision VALUE=preapprove>Preapprove thread, by Subject:</INPUT> -->
273 <INPUT TYPE=radio NAME=thread_decision VALUE=ban
274 ONCLICK=\"alert( 'Banning a thread is a controversial practice'); \"
275 >Ban Entire Thread By Subject (Careful!)</INPUT>
276 <INPUT TYPE=radio NAME=thread_decision VALUE=watch>Put Entire thread on a Watch, by Subject:</INPUT>
280 NOTE: Decisions to ban and preapprove posters and threads can be reversed by
281 logging in as \"admin\" and editing respective lists of preapproved
282 and banned threads and posters.
285 &link_to_help( "filter-lists", "automatic filtering and filter lists, blacklisting and preapproved threads." );
287 print "Be really careful about blacklisting of everyone except spammers.</I><BR><BR>
289 <INPUT TYPE=radio NAME=next_screen VALUE=single CHECKED>
290 Review ONE article in next screen
291 <INPUT TYPE=radio NAME=next_screen VALUE=multiple>
292 Review multiple articles in next screen
295 <INPUT TYPE=submit VALUE=\"Submit\">
296 <INPUT TYPE=submit NAME=skip_submit VALUE=\"Skip\">
297 <INPUT TYPE=reset VALUE=\"Reset\">
301 print "<BR><A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
302 "moderator=$moderator&password=$password>Change Password</A>";
308 # WebSTUMP administrative screen
309 sub webstump_admin_screen {
311 &verify_admin_password;
313 my $password = $request{'password'};
315 &begin_html( "WebSTUMP Administration" );
317 <FORM METHOD=$request_method action=$base_address>
318 <INPUT NAME=action VALUE=admin_add_newsgroup TYPE=hidden>
319 <INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
324 Create a new newsgroup on the server:<BR>
326 Newsgroup:<BR> <INPUT NAME=newsgroup_name VALUE=\"\" SIZE=50><BR>
327 Address to send approved/rejected messages <BR>
328 <INPUT NAME=newsgroup_approved_address VALUE=\"\" SIZE=30><BR>
329 Admin Password For this group:<BR> <INPUT NAME=newsgroup_password VALUE=\"\" SIZE=10><BR>
330 <INPUT TYPE=submit VALUE=\"Submit\">
331 <INPUT TYPE=reset VALUE=\"Reset\"><HR>
334 print "</FORM>\n\n<PRE>\n";
339 # WebSTUMP "add newsgroup" function
340 sub admin_add_newsgroup {
342 &verify_admin_password;
344 my $newsgroup = &required_parameter( 'newsgroup_name' );
346 $newsgroup =~ s/\///g;
347 $newsgroup = &untaint( $newsgroup );
349 my $address = &required_parameter( 'newsgroup_approved_address' );
350 my $password = &required_parameter( 'newsgroup_password' );
352 &user_error( "Newsgroup $newsgroup already exists" )
353 if defined $newsgroups_index{$newsgroup};
355 &user_error( "Password may only contain letters and digits" )
356 if( ! ($password =~ /^[a-zA-Z0-9]+$/ ) );
358 &begin_html( "WebSTUMP Administration: Newsgroup created" );
362 print "Adding $newsgroup to $webstump_home/config/newsgroups.lst...";
363 mkdir "$webstump_home/queues/$newsgroup", 0755;
366 $dir = "$webstump_home/config/newsgroups/$newsgroup";
368 print "Creating $dir...";
372 print "Creating files in $dir...";
374 &append_to_file( "$dir/blacklist", "" );
375 &append_to_file( "$dir/address.txt", "$address\n" );
376 &append_to_file( "$dir/moderators", "ADMIN \U$password\n" );
377 &append_to_file( "$dir/rejection-reasons",
378 "offtopic::a blatantly offtopic article, spam
379 harassing::message of harassing content
380 charter::message poorly formatted
382 &append_to_file( "$dir/whitelist", "" );
394 $web_subdir = pop( @_ );
396 $newsgroup = pop( @_ );
398 opendir( SUBDIR, $subdir );
402 while( $_ = readdir( SUBDIR ) ) {
403 my $file = "$subdir/$_";
404 next if( ! -f $file || ! -r $file );
405 my $extension = $file;
406 $extension =~ s/^.*\.//;
407 $extension = "\L$extension";
409 if( $extension eq "gif" || $extension eq "jpg" || $extension eq "jpeg" ) {
410 print "<CENTER> <IMG SRC=$base_address_for_files/queues/$newsgroup/$web_subdir/$_></CENTER><HR>\n";
414 $filename =~ s/^.*\///;
415 next if $filename eq "skeleton.skeleton"
416 || $filename eq "headers.txt"
417 || $filename eq "full_message.txt"
418 || $filename eq "text.files.lst"
419 || $filename eq "stump-prolog.txt"
420 || $filename eq "stump-warning.txt"
421 || $filename =~ /msg-.*\.doc/;
423 &print_image( "no_image.gif", "security warning" );
424 print "<B>Non-image attachment:</B><CODE>$filename</CODE> NOT SHOWN for security reasons.<BR>\n";
430 # prints warning if there is warning stored about the article
431 sub print_article_warning { # short-subdir
432 my $file = pop( @_ );
434 my $warning_file = &article_file_name( $file ) . "/stump-warning.txt";
436 if( -r $warning_file ) {
437 open( WARNING, $warning_file );
438 while ($warning = <WARNING>) {
439 next unless $warning =~ m/\S/;
440 $warning =~ s/\&/&/g;
441 $warning =~ s/</</g;
442 $warning =~ s/>/>/g;
443 &print_image( "star.gif", "warning" );
444 print "<FONT COLOR=red>$warning</FONT><br>\n";
453 sub get_queue_list ($) {
454 my ($newsgroup) = @_;
455 my $dir = "$queues_dir/$newsgroup";
458 opendir(QUEUED, $dir) or &error("could not open directory $dir");
462 my $subdir= scalar readdir(QUEUED);
463 last unless defined $subdir;
465 my $subpath= "$dir/$subdir";
466 next if $subdir =~ /^\.+/;
467 next unless -d $subpath;
469 if (!stat "$subpath/stump-warning.txt") {
470 $!==&ENOENT or die "$subpath $!";
473 $sortkey= (stat _)[9];
475 $sortkeys{$subdir}= $sortkey;
478 my @articles= sort { $sortkeys{$a} <=> $sortkeys{$b} } keys %sortkeys;
479 return ($dir, @articles);
482 # main moderation page
483 sub html_moderation_screen {
484 my $newsgroup = &required_parameter( 'newsgroup' );
485 my $moderator = $request{'moderator'};
486 my $password = $request{'password'};
489 if( $request{'next_screen'} eq 'single' ) {
490 # we show a single article if the user so requested.
491 # just get the first article from the queue if any, otherwise show
492 # an empty main screen.
494 my ($dir, @articles)= get_queue_list($newsgroup);
497 for ($i=0; $i<@articles; $i++) {
498 my $subdir= shift @articles;
499 push @articles, $subdir;
500 last if $request{"decision_$subdir"};
503 while( $subdir = shift @articles ) {
504 if( -d "$dir/$subdir" && !($subdir =~ /^\.+/)
505 && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
506 &html_moderate_article( $subdir );
511 # otherwise just show the moderator an empty main screen.
514 &begin_html( "Main Moderation Screen: $newsgroup" );
515 print "Welcome to the main moderation screen. Its main purpose is to
516 help you process most messages extremely quickly. For every message, it
517 presents you who sent it, as well as the first three non-blank lines.
518 For those messages where the decision is obvious, simply select your
519 decision (approve/reject etc) and click submit. For those messages which
520 you would like to review in more details, do not select anything and
521 use Review/Comment function from this screen or from a subsequent screen.
522 Remember that if you do not make any decision, the article would stay in the
525 &read_rejection_reasons;
527 my ($dir, @articles)= get_queue_list($newsgroup);
530 <FORM METHOD=$request_method action=$base_address>
531 <INPUT NAME=action VALUE=approval_decision TYPE=hidden>";
532 &html_print_credentials;
534 my $file, $subject = "No Subject", $from = "From nobody";
535 my $form_not_empty = "";
536 my $article_count = 0;
538 while( ($subdir = shift @articles) && $article_count++ < 40 ) {
540 if( -d "$dir/$subdir" && !($subdir =~ /^\.+/)
541 && open( PROLOG, "$dir/$subdir/stump-prolog.txt" ) ) {
544 if( /^Real-Subject: /i ) {
548 s/^Real-Subject: //g;
549 $subject = substr( $_, 0, 50 );
550 } elsif( /^From: /i ){
554 $from = substr( $_, 0, 50 );
560 print "<HR><B>$from: $subject</B>(";
561 print "<A HREF=$base_address?action=moderate_article&newsgroup=$newsgroup&" .
562 "moderator=$moderator&password=$password&file=$subdir>Review/Comment/Preapprove</A>)<BR>\n";
563 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=approve>Approve\n";
564 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=skip>Leave\n";
565 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=leave>Back of queue\n";
566 foreach (@short_rejection_reasons) {
567 print "<INPUT TYPE=radio NAME=\"decision_$file\" VALUE=\"reject $_\">Reject \u$_\n";
572 &print_article_warning( $file );
578 while( ($_ = <PROLOG>) && $i < 5 ) {
585 print "] " . substr( $_, 0, 75 ) . "\n";
591 $form_not_empty = "yes";
593 $article_count += &print_images( $newsgroup, "$dir/$subdir", $subdir );
597 if( $form_not_empty ) {
598 print "<HR> <INPUT TYPE=submit VALUE=Submit>
599 <INPUT TYPE=reset VALUE=Reset>
604 No articles present in the queue
605 <INPUT TYPE=submit VALUE=Refresh>
609 print "<A HREF=$base_address?action=change_password&newsgroup=$newsgroup&" .
610 "moderator=$moderator&password=$password>Change Password</A>";
614 print "<FORM METHOD=$request_method action=$base_address>";
615 &html_print_credentials;
616 print "<INPUT NAME=action VALUE=moderator_admin TYPE=hidden>
617 <INPUT TYPE=submit VALUE=\"Manage pass/grey/block-lists\">
623 # prints hidden fields -- credentials
624 sub html_print_credentials {
625 my $newsgroup = $request{'newsgroup'};
626 my $moderator = $request{'moderator'};
627 my $password = $request{'password'};
630 <INPUT NAME=newsgroup VALUE=\"$newsgroup\" TYPE=hidden>
631 <INPUT NAME=moderator VALUE=\"$moderator\" TYPE=hidden>
632 <INPUT NAME=password VALUE=\"$password\" TYPE=hidden>\n";
635 # newsgroup admin page
636 sub html_newsgroup_management {
637 &begin_html( "Administer $request{'newsgroup'}" );
639 print "All usernames and passwords are not case sensitive.\n";
640 print "<HR>Use this form to add new moderators or change passwords:<BR>
641 <FORM METHOD=$request_method action=$base_address>
642 <INPUT NAME=action VALUE=add_user TYPE=hidden>";
643 &html_print_credentials;
645 Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
647 Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
649 <INPUT TYPE=submit VALUE=\"Add/Change\">
650 <INPUT TYPE=reset VALUE=Reset>
654 print "<HR>Use this form to delete moderators:<BR>
655 <FORM METHOD=$request_method action=$base_address>
656 <INPUT NAME=action VALUE=delete_user TYPE=hidden>";
657 &html_print_credentials;
659 Username: <INPUT NAME=user VALUE=\"\" SIZE=20>
661 <INPUT TYPE=submit VALUE=\"Delete Moderator\">
662 <INPUT TYPE=reset VALUE=Reset>
665 <FORM METHOD=$request_method action=$base_address>
666 <INPUT NAME=action VALUE=edit_list TYPE=hidden>";
667 &html_print_credentials;
669 Configuration List: <SELECT NAME=list_to_edit>
671 <OPTION VALUE=good.posters.list>Good Posters List
672 <OPTION VALUE=watch.posters.list>Suspicious Posters List
673 <OPTION VALUE=bad.posters.list>Banned Posters List
674 <OPTION VALUE=good.subjects.list>Good Subjects List
675 <OPTION VALUE=watch.subjects.list>Suspicious Subjects List
676 <OPTION VALUE=bad.subjects.list>Banned Subjects List
677 <OPTION VALUE=watch.words.list>Suspicious Words List
678 <OPTION VALUE=bad.words.list>Banned Words List
681 <INPUT TYPE=submit VALUE=\"Edit\">
682 <INPUT TYPE=reset VALUE=Reset>";
684 &link_to_help( "filter-lists", "filtering lists" );
689 List of current moderators:<P>
693 foreach (keys %moderators) {
704 sub edit_configuration_list {
706 my $list_to_edit = &required_parameter( 'list_to_edit' );
708 $list_to_edit = &check_config_list( $list_to_edit );
710 my $list_file = &full_config_file_name( $list_to_edit );
712 my $list_content = "";
714 if( open( LIST, $list_file ) ) {
715 $list_content .= $_ while( <LIST> );
719 $list_content =~ s/\&/&/g;
720 $list_content =~ s/</</g;
721 $list_content =~ s/>/>/g;
723 &begin_html( "Edit $list_to_edit" );
726 " <FORM METHOD=$request_method action=$base_address>
727 <INPUT NAME=action VALUE=set_config_list TYPE=hidden>
728 <INPUT NAME=list_to_edit VALUE=$list_to_edit TYPE=hidden>";
729 &html_print_credentials;
730 &link_to_help( $list_to_edit, "$list_to_edit" );
733 <TEXTAREA NAME=list rows=20 COLS=50>
734 $list_content</TEXTAREA>
737 <INPUT TYPE=submit VALUE=\"Set\">
744 # password change page
745 sub html_change_password{
746 &begin_html( "Change Password" );
748 print "All usernames and passwords are not case sensitive.\n";
749 print "<HR>Use this form to change your password:<BR>
750 <FORM METHOD=$request_method action=$base_address>
751 <INPUT NAME=action VALUE=validate_change_password TYPE=hidden>";
752 &html_print_credentials;
755 New Password: <INPUT NAME=new_password VALUE=\"\" SIZE=20>
757 <INPUT TYPE=submit VALUE=Submit>
758 <INPUT TYPE=reset VALUE=Reset>
766 # newsgroup creation form
767 sub init_request_newsgroup_creation{
768 my $newsgroup = &required_parameter( 'newsgroup' );
770 &begin_html( "Request Creation of $newsgroup" );
772 print "This page helps you ask the system administrator of your domain
773 to create <B>$newsgroup</B> on your server. Type in your domain name and
774 click SUBMIT. An email will be sent to news\@domain and usenet\@domain
775 and postmaster\@domain
776 asking them to create your newsgroup. Please do NOT abuse this system.
777 NOTE: You can give the URL of this page to your group readers so that
778 they could request creation of their newsgroups by themselves.\n";
781 <FORM METHOD=$request_method action=$base_address>
782 <INPUT NAME=action VALUE=complete_newsgroup_creation_request TYPE=hidden>\n";
783 &html_print_credentials;
786 Domain Name ONLY: <INPUT NAME=domain_name VALUE=\"\" SIZE=40>
788 <INPUT TYPE=submit VALUE=Submit>
789 <INPUT TYPE=reset VALUE=Reset>
797 # newsgroup creation completion
798 sub complete_newsgroup_creation_request{
799 my $newsgroup = &required_parameter( 'newsgroup' );
800 my $domain_name = &required_parameter( 'domain_name' );
802 if( !($domain_name =~ /(^[a-zA-Z0-9\.-_]+$)/) ) {
803 &user_error( "invalid domain name" );
809 my $request = "To: news\@$domain_name, usenet\@$domain_name, postmaster\@$domain_name
810 Subject: Please create $newsgroup (Moderated)
811 From: devnull\@algebra.com ($newsgroup Moderator)
812 Organization: stump.algebra.com
814 Dear News Administrator:
816 A user of $domain_name has requested that you create a newsgroup
818 $newsgroup (Moderated)
820 on your server. $newsgroup
821 is a legitimately created moderated newsgroup that is available worldwide.
823 Thank you very much for your help and cooperation.
827 - Moderator of $newsgroup.
831 &email_message( $request, "news\@$domain_name" );
832 &email_message( $request, "usenet\@$domain_name" );
833 &email_message( $request, "postmaster\@$domain_name" );
835 &begin_html( "Request to create $newsgroup sent" );
837 print "The following request has been sent:<HR><PRE>\n";
839 print "$request</PRE>\n";
846 my $topic_name = &required_parameter( "topic" );
848 $topic_name =~ s/\///g;
849 $topic_name =~ s/\.\.//g;
850 $topic_name = &untaint( $topic_name );
852 my $file = "$webstump_home/doc/help/$topic_name.html";
854 &error( "Topic $topic_name not found in $file." )
857 open( FILE, "$file" );
859 $help .= $_ while( <FILE> );
862 $help =~ s/##/$base_address?action=help&topic=/g;
864 &begin_html( "$topic_name" );