aboutsummaryrefslogtreecommitdiff
path: root/web/NetUseAdminWeb/lib
diff options
context:
space:
mode:
Diffstat (limited to 'web/NetUseAdminWeb/lib')
-rw-r--r--web/NetUseAdminWeb/lib/NetUseAdminWeb.pm302
1 files changed, 302 insertions, 0 deletions
diff --git a/web/NetUseAdminWeb/lib/NetUseAdminWeb.pm b/web/NetUseAdminWeb/lib/NetUseAdminWeb.pm
new file mode 100644
index 0000000..9524309
--- /dev/null
+++ b/web/NetUseAdminWeb/lib/NetUseAdminWeb.pm
@@ -0,0 +1,302 @@
+package NetUseAdminWeb;
+
+# NetUseMod is a program to moderate Usenet posts via web interface.
+# Copyright (C) 2024 Salahuddin <salahuddin@member.fsf.org>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+use Dancer2;
+use File::Copy 'move';
+use HTML::Entities;
+
+our $VERSION = '0.1';
+
+my $post_to_usenet = 1; # toggle for testing
+my $usenet_host = 'YOUR_USENET_PROVIDER';
+my $usenet_port = '119'; # production 536 ssl
+my $rnews_location = '/usr/bin/rnews';
+# set user and password in /etc/news/passwd.nntp
+# to submit news to your newsgroup provider.
+
+my $send_rec_ack = 1;
+my $noreply_mail = 'no-reply@YOUR_DOMAIN';
+
+my $mail_format = 'YOUR_HOME_DIR/NetUseMod/scripts/mail_format.sh';
+my $admin_user = 'XXXXXXXX';
+my $admin_pass = 'XXXXXXXX';
+
+# example: /home/username/
+my $dir_base = 'YOUR_HOME_DIR';
+my $dir_incoming = 'incoming_news';
+my $dir_processed = 'processed_news';
+my $dir_rejected = 'rejected_news';
+
+
+sub sendReceiveAck {
+ my ($from, $subject, $description) = @_;
+ system('echo "' . $description . '." | mail -s "Re: ' . $subject . '" -r ' . $noreply_mail . ' \'' . $from . '\'');
+}
+
+sub postToNewsGroup {
+ my $processed_dest_file = shift;
+ system($rnews_location . ' -h ' . $usenet_host . ' -P ' . $usenet_port . ' -S ' . $usenet_host . ' < ' . $processed_dest_file);
+}
+
+sub formatNews {
+ my ($filename, $processed_dest_file) = @_;
+ system('cat ' . $filename . ' | ' . $mail_format . ' > ' . $processed_dest_file);
+}
+
+sub getFileData {
+ my $filename = shift;
+ if ($filename eq '') {
+ return '';
+ }
+ open(FH, '<', $filename) or die $!;
+ my $file_data = '';
+ while(<FH>){
+ $file_data .= $_;
+ }
+ close(FH);
+ return $file_data;
+}
+
+hook before => sub {
+ if (!session('user') &&
+ request->path !~ m{^/login} ) {
+ forward '/login', { requested_path => request->path };
+ }
+};
+
+get '/' => sub {
+ template 'index';
+};
+
+sub get_news_list {
+ my $dir_type = shift;
+ my $dirname = $dir_base . $dir_type . "/";
+ opendir my($dh), $dirname or die "Couldn't open dir '$dirname': $!";
+ my @files = readdir $dh;
+ closedir $dh;
+ # remve .. . entries.
+ for(my $index = 0; $index <= $#files; $index++){
+ if ($files[$index] eq '.') {
+ splice(@files, $index, 1);
+ #index value changed
+ last;
+ }
+ }
+ for(my $index = 0; $index <= $#files; $index++){
+ if ($files[$index] eq '..') {
+ splice(@files, $index, 1);
+ #index value changed
+ last;
+ }
+ }
+ my %file_list;
+ foreach(@files) {
+ my $fh = $dirname . '/' . $_;
+ my $epoch_timestamp = (stat($fh))[8];
+ my $timestamp = localtime($epoch_timestamp);
+ $file_list{$_} = $timestamp;
+ }
+ return %file_list;
+}
+
+get '/listincomingnews' => sub {
+ my %file_list = get_news_list($dir_incoming);
+ my $listnews = '';
+ for(keys %file_list){
+ $listnews .= "<tr>";
+ $listnews .= '<td>' . $_ . '</td>';
+ $listnews .= '<td>' . $file_list{$_} . '</td>'; # created time
+ $listnews .= '<td><a href="/viewfile?filename=' . $_ . '&dirtype=' .$dir_incoming . '">view</a></td>';
+ $listnews .= "</tr>";
+ }
+ template 'listnews', { listnews => $listnews };
+};
+
+get '/viewfile' => sub {
+ my $filename_param = query_parameters->get('filename');
+ my $dir_type = query_parameters->get('dirtype');
+ my $filename = $dir_base . $dir_type . '/' . $filename_param;
+ my $file_data = '';
+ $file_data = getFileData($filename);
+ if ($file_data eq '') {
+ template 'generic', { message => "emtpy" };
+ }
+ $file_data = encode_entities($file_data);
+ $file_data =~ s/\n/<br>/g;
+ my $accept_reject_html_block = '';
+ if ($dir_type eq $dir_incoming) {
+ my $accept_html_button = '<a href="/acceptnews?filename=' . $filename_param . '">Accept</a>';
+ my $reject_html_button = '<a href="/rejectnews?filename=' . $filename_param . '">Reject</a>';
+ $accept_reject_html_block = $accept_html_button . '&nbsp;&nbsp;' . $reject_html_button;
+ }
+ template 'viewnews', { file_data => $file_data, accept_reject_html_block => $accept_reject_html_block };
+};
+
+get '/listprocessednews' => sub {
+ my %file_list = get_news_list($dir_processed);
+ my $listnews = '';
+ for(keys %file_list){
+ $listnews .= "<tr>";
+ $listnews .= '<td>' . $_ . '</td>';
+ $listnews .= '<td>' . $file_list{$_} . '</td>'; # created time
+ $listnews .= '<td><a href="/viewfile?filename=' . $_ . '&dirtype=' . $dir_processed . '">view</a></td>';
+ $listnews .= "</tr>";
+ }
+ template 'listnews', { listnews => $listnews };
+};
+
+get '/listrejectednews' => sub {
+ my %file_list = get_news_list($dir_rejected);
+ my $listnews = '';
+ for(keys %file_list){
+ $listnews .= "<tr>";
+ $listnews .= '<td>' . $_ . '</td>';
+ $listnews .= '<td>' . $file_list{$_} . '</td>'; # created time
+ $listnews .= '<td><a href="/viewfile?filename=' . $_ . '&dirtype=' . $dir_rejected . '">view</a></td>';
+ $listnews .= "</tr>";
+ }
+ template 'listnews', { listnews => $listnews };
+};
+
+get '/acceptnews' => sub {
+ my $filename_param = query_parameters->get('filename');
+ my $filename = $dir_base . '/' . $dir_incoming . '/' . $filename_param;
+ my $file_data = '';
+ $file_data = getFileData($filename);
+ if ($file_data eq '') {
+ template 'generic', { message => "emtpy" };
+ }
+ $file_data = encode_entities($file_data);
+ $file_data =~ s/\n/<br>/g;
+ template 'acceptnews', { file_data => $file_data, filename_param => $filename_param };
+};
+
+post '/acceptnews' => sub {
+ my $filename_param = body_parameters->get('filename_param');
+ my $filename = $dir_base . '/' . $dir_incoming . '/' . $filename_param;
+ my $dest_filename = $dir_base . '/' . $dir_processed . '/' . $filename_param;
+ my $processed_dest_file = $dest_filename . '_processed';
+ formatNews($filename, $processed_dest_file);
+ if ($post_to_usenet eq 1) {
+ postToNewsGroup($processed_dest_file);
+ }
+ move($filename, $dest_filename)or die "The move operation failed: $!";
+ template 'generic', { message => 'Success' };
+};
+
+get '/rejectnews' => sub {
+ my $filename_param = query_parameters->get('filename');
+ my $filename = $dir_base . '/' . $dir_incoming . '/' . $filename_param;
+ my $file_data = '';
+ $file_data = getFileData($filename);
+ if ($file_data eq '') {
+ template 'generic', { message => "emtpy" };
+ }
+ $file_data = encode_entities($file_data);
+ $file_data =~ s/\n/<br>/g;
+ template 'rejectnews', { file_data => $file_data, filename_param => $filename_param };
+};
+
+sub parseFromAndSubject {
+ my $message = shift;
+ my $from = '';
+ my $subject = '';
+ my @message_array = split(/\n/, $message);
+
+ foreach(@message_array) {
+ # first empty line
+ if(/^$/) {
+ last;
+ }
+ if( /^From: / ) {
+ $from = $_;
+ $from=~ s/^From: //i;
+ } elsif( /^Subject: / ) {
+ $subject = $_;
+ $subject =~ s/^Subject: //i;
+ }
+ }
+ # trim
+ $from =~ s/^\s+|\s+$//g;
+ $subject =~ s/^\s+|\s+$//g;
+ return ($from, $subject);
+}
+
+post '/rejectnews' => sub {
+ my $submitbutton = body_parameters->get('submitbutton');
+ my $filename_param = body_parameters->get('filename_param');
+ my $reject_reason = body_parameters->get('reject_reason');
+ my $filename = $dir_base . '/' . $dir_incoming . '/' . $filename_param;
+ my $dest_filename = $dir_base . '/' . $dir_rejected . '/' . $filename_param;
+
+ if (index($submitbutton, 'Send reject notification') != -1) {
+ # TODO: send notification
+ if ($send_rec_ack eq 1) {
+ open(FH, '<', $filename) or die $!;
+ my $file_data = '';
+ while(<FH>){
+ # first empty line
+ if(/^$/) {
+ last;
+ }
+ $file_data .= $_;
+ }
+ close(FH);
+ my ($from, $subject) = parseFromAndSubject($file_data);
+ # TODO: check return value
+ sendReceiveAck($from, $subject, $reject_reason);
+ }
+ }
+ # add error in error variable
+ move($filename, $dest_filename)or die "The move operation failed: $!";
+
+ my $message = 'Success';
+ template 'generic', { message => $message };
+};
+
+get '/login' => sub {
+ if (session('user')) {
+ redirect '/';
+ } else {
+ template 'login', { path => param('requested_path') };
+ }
+};
+
+post '/login' => sub {
+ my $user_value = body_parameters->get('user');
+ my $pass_value = body_parameters->get('pass');
+ my $error_message = '';
+
+ if($user_value eq $admin_user && $pass_value eq $admin_pass) {
+ session user => $user_value;
+ redirect body_parameters->get('path') || '/';
+ } else {
+ $error_message = 'Invalid login/password.';
+ }
+
+ if($error_message ne '') {
+ return template 'generic', { message => $error_message};
+ }
+};
+
+get '/logout' => sub {
+ app->destroy_session;
+ redirect '/';
+};
+
+true;