#!/usr/pkg/bin/perl -w
#
# slerm - SLugmax's gERM
# Based on germ - let your ideas germinate (c) 2009, Wesley Teal,
#
[email protected]
# Modifications 2009-2017 by
[email protected]
#
# distributed under the MIT/X Consortium License, see LICENSE for details
#
# $Id: slerm.cgi,v 1.8 2017/05/08 13:36:26 slugmax Exp $
#
use strict;
use Text::Wrap;
$Text::Wrap::columns=68;
use Fcntl qw(:flock);
use POSIX qw(strftime);
use File::Basename;
use Mail::Mailer;
use English;
# Nix the PATH and prevent SETUID/SETGID
$ENV{PATH} = "";
$EUID = $UID;
$EGID = $GID;
# === Configurable Variables ===
# this script's name
my $name = "cgi-bin/slerm";
# the server that is hosting the script
my $server = "sdf.org";
# the port number the server uses
my $port = 70;
# the directory used in gopher links, make sure not to end with a "/"
my $base_dir ="/users/USERNAME";
# full path name to the directory you store your *.post, header.txt, and div.txt
# files in, make sure not to end with a "/"
my $dir = "/ftp/pub$base_dir/dat";
# the maximum number of posts you want displayed per phlog page
my $max_posts = 10;
# Whether or not to send an email with any comments (0 means no email)
my $EMAIL_COMMENTS = 1;
# Email to send post comments to
my $comment_email = q{
[email protected]};
# If set to 1, post comments in reverse chronological order.
my $COMMENT_ORDER_REVERSE = 0;
# File extension for posts. Comment files will start with this
# extension, with '.cmt' added to the end
my $post_ext = "post";
# Permalink text. Shown as a link on shortened posts or archive pages
my $permalink_text = "View Post or Comment... (NN)";
# List of books you're reading
my $bookshelf = "$dir/bookshelf.txt";
# === End Configurable Variables ===
my $serv_string = "\tnull\ttext\t70";
my $ver = '$Id: slerm.cgi,v 1.8 2017/05/08 13:36:26 slugmax Exp $';
my $header = "$dir/header.txt";
my $div = "$dir/div.txt";
#my $footer = "i$serv_string\r\nipowered by $ver\r\n";
my $footer = "$dir/footer.txt";
my @archive;
my @posts = <$dir/*.$post_ext>;
my @sorted_posts = sort { -M $a <=> -M $b } @posts;
my @sorted_copy = @sorted_posts;
if ($#sorted_posts > $max_posts - 1) {
until ($#sorted_posts == $max_posts - 1) {
unshift @archive, pop @sorted_posts;
}
}
sub num_comments {
my $comment_file = shift;
my $cnt = 0;
$comment_file .= q{.cmt};
open (my $fh, "<", "$dir/$comment_file") || return 0;
while (<$fh>) {
$cnt++ if ( /---/ );
}
close ($fh);
return $cnt
}
sub email_comment {
my ($recipient,$body) = @_;
my $from = $recipient;
my $subject = "New Comment Posted to Your Phlog!";
my $mailer = Mail::Mailer->new("sendmail");
$mailer->open({ From => $from,
To => $recipient,
Subject => $subject,
})
or return;
print $mailer $body or return;
$mailer->close( );
}
sub nav_links {
my $phlog_string = "\t$base_dir/$name\t$server\t$port";
my $main_string = "\t$base_dir\t$server\t$port";
print "i$serv_string\r\n";
print "1Back to Phlog Index$phlog_string\r\n";
print "1Back to The Cave$main_string\r\n";
}
sub filedate {
my $file = shift;
return strftime( "%A, %B %d, %Y at %R", localtime((stat ($file))[9]));
}
# Returns true if the given file has the given tag in its #Tags.. line
sub has_tag {
my $file = shift;
my $tag = shift;
my $found = 0;
open (my $fh, "<", "$file") || return 0;
while (<$fh>) {
next if ( !/^#tags/i );
last if ( $INPUT_LINE_NUMBER > 2 );
if ( /\b$tag\b/i ) {
$found = 1;
last;
}
}
close ($fh);
return $found;
}
# Prints the lines in the given file as inline-text. Parses tags and
# shortens posts if appropriate
sub printlines {
my $args = shift;
my $file = $$args{file};
my $die_on_error = $$args{die_on_error};
my $show_tags = $$args{show_tags};
my $shorten_post = $$args{shorten_post};
my $fh;
if ( $die_on_error ) {
open ($fh, "<", "$file") || die "3Unable to open $file: $!$serv_string\r\n";
} else {
open ($fh, "<", "$file") || return;
}
while (<$fh>) {
chomp;
my $tmpline = $_;
if ( $show_tags && $tmpline =~ /^#tags/i ) {
my $tagline = $_;
$tagline =~ s/^#tags\s+//i;
$tagline =~ s/\s*,\s*/,/g;
my @tags = split(/,/,$tagline);
print "iTags:$serv_string\r\n";
foreach my $tag ( @tags ) {
print "1$tag\t$base_dir/$name?tag-$tag\t$server\t$port\r\n";
}
}
# If we detect a gophermap-style link, just print it
if ( $tmpline =~ /^[\dceghisIMT].+?\t/ ) {
print "$tmpline\r\n";
} else {
print "i$tmpline$serv_string\r\n" unless ( $tmpline =~ /^#tags/i || $tmpline =~ /--more--/i );
}
last if ( $shorten_post && $tmpline =~ /--more--/i );
}
close ($fh);
}
my $query;
if (exists $ENV{QUERY_STRING}) {
$query = $ENV{QUERY_STRING};
} else {
$query = join(' ',@ARGV);
}
# Show an individual post or comment. Any tags and the entire
# post/comment are always shown
if ( $query =~ /^(.+\.$post_ext)$/ ) { # show individual posts/comments
my $go_string = "\t$base_dir/$name?$query\t$server\t$port";
my $com_string = "\t$base_dir/$name\t$server\t$port";
my $cmt = "$query.cmt";
printlines({ file => $header, die_on_error => 0, show_tags => 0 });
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
print "1Back to Phlog Index$com_string\r\n";
print "i$serv_string\r\n";
printlines({ file => "$dir/$query", die_on_error => 1, show_tags => 1 });
print "i$serv_string\r\n";
print "7Leave Comment$com_string\r\n";
$Text::Wrap::separator = "$serv_string\r\n";
print wrap("i", "i", "TO LEAVE A COMMENT enter \"$query\" at the prompt followed by a space and then your comment of 250 words or less."), "$serv_string\r\n";
$Text::Wrap::separator = "\r\n";
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
if (-e "$dir/$cmt") {
printlines({ file => "$dir/$cmt", die_on_error => 1, show_tags => 0 });
}
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
# Show an archive page. Each archive page will have $max_posts posts,
# with a link to the next, previous (if appropriate) and main pages at
# the bottom
} elsif ( $query =~ /^archive(\d+)$/ ) {
my $num = $1;
my $min = $max_posts * $num - $max_posts;
my $max = $max_posts * $num - 1;
$max = $#archive if ($max > $#archive);
printlines({ file => $header, die_on_error => 0, show_tags => 0 });
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
foreach (@archive[$min .. $max]) {
my $p_name = basename $_;
my $go_string = "\t$base_dir/$name?$p_name\t$server\t$port";
print "i",filedate($_),"$serv_string\r\n";
print "i$serv_string\r\n";
printlines({ file => $_, die_on_error => 1, show_tags => 0, shorten_post => 1 });
my $tmp = $permalink_text;
$tmp =~ s/NN/num_comments($p_name)/e;
print "1$tmp$go_string\r\n";
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
}
if ($#archive > $max) {
my $next_page = $num + 1;
print "1Older Posts\t$base_dir/$name?archive$next_page\t$server\t$port\r\n";
}
if ($num > 1) {
my $prev_page = $num - 1;
print "1Newer Posts\t$base_dir/$name?archive$prev_page\t$server\t$port\r\n";
}
my $com_string = "\t$base_dir/$name\t$server\t$port";
print "1Back to Phlog Index$com_string\r\n";
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
# Tag support - given a tag, lists all posts containing that tag
} elsif ( $query =~ /^tag-(\w+)$/ ) {
my $tag = $1;
my $found = 0;
printlines({ file => $header, die_on_error => 0, show_tags => 0 });
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
foreach my $post (@sorted_copy) {
next if ( !has_tag($post,$tag) );
my $p_name = basename $post;
my $go_string = "\t$base_dir/$name?$p_name\t$server\t$port";
$found = 1;
print "i",filedate($post),"$serv_string\r\n";
print "i$serv_string\r\n";
printlines({ file => $post, die_on_error => 1, show_tags => 0, shorten_post => 1 });
my $tmp = $permalink_text;
$tmp =~ s/NN/num_comments($p_name)/e;
print "1$tmp$go_string\r\n";
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
}
print "iNo posts found matching the tag \"$tag\"$serv_string\r\n" if ( !$found );
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
# Comments - one file per post is reserved for comments, new comments
# are added to a comment file if it already exists
} elsif ($query =~ /^(.+\.$post_ext)\s+([\w\s[:punct:]]+)$/ && length($query) < 950) {
my $post = $1;
my $comment_string = $2;
if ( ! -e "$dir/$post" ) {
print "3Sorry, we could not find the post '$dir/$post'. Please enter the name of the post you wish to comment on, followed by a space and the comment.$serv_string\r\n";
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
}
my $comfile = "$dir/$post.cmt";
my $tmp = "$comfile.tmp";
my $go_string = "\t$base_dir/$name?$post\t$server\t$port";
open (my $new, ">", "$tmp");
flock ($new, LOCK_EX);
$comment_string = wrap("", "",$comment_string);
email_comment($comment_email,qq{$post:\n\n$comment_string}) if $EMAIL_COMMENTS;
if ( $COMMENT_ORDER_REVERSE ) {
print $new filedate($tmp),"\n";
print $new $comment_string,"\n";
print $new "\n---\n";
}
if (-e $comfile) {
open (my $old, "<", "$comfile");
flock ($old, LOCK_EX);
while (my $line = <$old>) {
print $new $line;
}
close $old;
}
if ( !$COMMENT_ORDER_REVERSE ) {
print $new "\n---\n";
print $new filedate($tmp),"\n";
print $new $comment_string,"\n";
}
close $new;
rename ($tmp, $comfile) || die "3Error trying to rename $tmp to $comfile: $!\r\n";
chmod 0644, $comfile;
print "iYour comment has been posted.$serv_string\r\n";
print "1View your comment$go_string\r\n";
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
} elsif ($query =~ /(.+\.$post_ext)\s+[\w[:punct:]]+/ && length($query) >= 950) {
print "3Sorry, comment should be less than 950 characters$serv_string";
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });
exit;
}
# Main index view - this is what users see initially. No tags are
# shown, and post shortening is enabled
printlines({ file => $header, die_on_error => 0, show_tags => 0 });
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
if ( -e $bookshelf ) {
printlines({ file => $bookshelf, die_on_error => 0, show_tags => 0 });
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
}
foreach (@sorted_posts) {
my $p_name = basename $_;
my $go_string = "\t$base_dir/$name?$p_name\t$server\t$port";
print "i",filedate($_),"$serv_string\r\n";
print "i$serv_string\r\n";
printlines({ file => $_, die_on_error => 1, show_tags => 0, shorten_post => 1 });
my $tmp = $permalink_text;
$tmp =~ s/NN/num_comments($p_name)/e;
print "1$tmp$go_string\r\n";
printlines({ file => $div, die_on_error => 0, show_tags => 0 });
}
# Print link to older posts (next page of archives) if needed
if ($#archive >= 0) {
print "1Older Posts\t$base_dir/$name?archive1\t$server\t$port\r\n";
}
nav_links();
printlines({ file => $footer, die_on_error => 0, show_tags => 0 });