From 8647234f2b9228dcb23853864f59a5f298e067df Mon Sep 17 00:00:00 2001 From: Miquel Lionel Date: Thu, 3 Mar 2022 20:48:00 +0100 Subject: Checkbox to send a mail to ppl on invite creation - Also added a checkbox to send a mail when generating an one-time encrypted form. --- gpigeon-template.cgi | 569 ++++++++++++++++++++++++++++++--------------------- styles.css | 6 +- 2 files changed, 334 insertions(+), 241 deletions(-) diff --git a/gpigeon-template.cgi b/gpigeon-template.cgi index a105841..63a9c1e 100755 --- a/gpigeon-template.cgi +++ b/gpigeon-template.cgi @@ -14,127 +14,132 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# Copyright (c) 2020-2021, Miquel Lionel +# Copyright (c) 2020-2022, Miquel Lionel use warnings; use strict; -use DBI; use Email::Valid; +use String::Random; +use DBI; use CGI qw(param); use CGI::Cookie; use CGI::Carp qw(fatalsToBrowser); use Crypt::Argon2 qw(argon2id_verify); use File::Path qw(mkpath rmtree); use File::stat; -use String::Random; delete @ENV{qw(IFS PATH CDPATH BASH_ENV)}; -$ENV{'PATH'} = q{bin_path_goes_here}; -my $rIP = $ENV{REMOTE_ADDR}; -my $uagent = $ENV{HTTP_USER_AGENT}; +$ENV{'PATH'} = q{/usr/bin}; +my $hostname = $ENV{'SERVER_NAME'}; +my $rIP = $ENV{REMOTE_ADDR}; +my $uagent = $ENV{HTTP_USER_AGENT}; my %text_strings = ( - addr => 'Address', - addr_ok => 'is valid!', - addr_nok => 'is not valid !', - addr_unknown => 'Unknown', - create_link_btn => 'Create link', - create_invite_btn => 'Create invite', - cookie_problems =>'You got a cookie problem.
Clean them and log in again', - delete_link_btn_text => 'Delete', - delete_links_btn_text => 'Delete all links', - disconnect_btn_text => 'Disconnect', - here => 'here', - landingpage_title => 'GPIGEON - Log in', - logout_btn_text => 'Logout', - loginbtn => 'Log in', - link_asker_field_label => "Asker's mail :", - link_del_ok => 'Successful removal !', - link_legend_textarea =>'Type your message below :', - link_ok_for => 'Generated a link for', - link_del_failed => 'Deletion failed and here is why : ', - link_generated_ok => "Here's the link", - mailto_body => 'Your link is ', - mailto_subject => 'Link to your one time GPG messaging form', - incorrect_ids => 'Username/password combination
is incorrect.
Try again.', - password_label => 'Password', - refresh_btn => 'Refresh', - theader_link => 'Link', - theader_for => 'For', - theader_deletion => 'Deletion', - theader_cdate => 'Created on', - username_label => 'Username', - web_title => 'GPIGEON.CGI - Main', - web_greet_msg => 'Hi and welcome. What will you do today ?', + addr => 'Address', + addr_ok => 'is valid!', + addr_nok => 'is not valid !', + addr_unknown => 'Unknown', + create_link_btn => 'Create link', + create_invite_btn => 'Create invite', + cookie_problems => 'You got a cookie problem.
Clean them and log in again', + checkbox_admin_user => 'User will be an admin', + checkbox_notiflinkbymail => 'Notify the user by mail about the link', + checkbox_invite_mailnotif => 'Send login details via an encrypted mail once the form is completed', + checkbox_mailinvite => 'Send mail about the invite', + optmail => '(Optional) Mail :', + delete_link_btn_text => 'Delete', + delete_links_btn_text => 'Delete all links', + delete_invites_btn_text => 'Delete all invites', + disconnect_btn_text => 'Disconnect', + logout_btn_text => 'Logout', + here => 'here', + landingpage_title => 'GPIGEON - Log in', + loginbtn => 'Log in', + link_asker_field_label => "Asker's mail :", + link_del_ok => 'Successful removal !', + link_legend_textarea => 'Type your message below :', + link_ok_for => 'Generated a link for', + link_del_failed => 'Deletion failed and here is why : ', + link_generated_ok => "Success! Here's the link", + mailto_body => 'Your link is ', + mailto_subject => 'Link to your one time GPG messaging form', + incorrect_ids => 'Username/password combination
is incorrect.
Try again.', + password_label => 'Password :', + refresh_btn => 'Refresh', + theader_link => 'Link', + theader_for => 'For', + theader_deletion => 'Deletion', + theader_creationdate => 'Created on', + username_label => 'Username :', + web_title => 'GPIGEON - Main', + web_greet_msg => 'Hi and welcome. What will you do today ?', ); - -sub DbGetLine { - my ($dbh, $query) = @_; - my $prep = $dbh->prepare( $query ); - my $exec = $prep->execute() or die $DBI::errstr; - - if ($exec < 0){ - print $DBI::errstr; - } - - while (my @rows = $prep->fetchrow_array()) { - my $row = $rows[0]; - return $row; - } -} - sub GetFileTable { - my ($dir ,$hidden_loginfield) = @_; - my @table = (); - opendir my $dir_hnd, "$dir" or die "[GetFileTable function] Can't open $dir: $!"; - while (readdir $dir_hnd) { + my ($dir ,$hidden_loginfield, $adminpan_field) = @_; + my @table = (); + opendir my $link_dir_handle, "$dir" or die "Can't open $dir: $!"; + while (readdir $link_dir_handle) { if ($_ ne '.' and $_ ne '..'){ my $linkfile_fn = $_; - my $linkstats = stat("$dir/$linkfile_fn"); - my $mtime = scalar localtime $linkstats->mtime; - my $link_asker = undef; - if (open my $f_hnd , '<', "$dir/$linkfile_fn"){ + my $linkstats= stat("$dir/$linkfile_fn"); + my $tiem = scalar localtime $linkstats->mtime; + my $link_asker = undef; + if (open my $linkfile_handle , '<', "$dir/$linkfile_fn"){ for (1..2){ - $link_asker = readline $f_hnd; + $link_asker = readline $linkfile_handle; $link_asker =~ s/q\{(.*?)\}//i; $link_asker = $1; } close $linkfile_handle; - my $for_field_body = qq{$link_asker}; + my $for_field_body = qq{$link_asker}; + + if (not defined $link_asker){ + $for_field_body = $text_strings{addr_unknown}; - if (not defined $link_asker){ - $for_field_body = $text_strings{addr_unknown}; } #create links table html push @table, qq{ - ici - $for_field_body - -
- $hidden_loginfield - - - -
- + ici + $for_field_body + +
+ $hidden_loginfield + $adminpan_field + + +
+ }; } else { close $linkfile_handle; - die "[GetFileTable function] Error: Can't open $linkfile_fn: $!"; + die 'Content-type: text/plain', "\n\n", "Error: Can't open $linkfile_fn: $!"; } } } - closedir $dir_hnd; + closedir $link_dir_handle; return @table; } +sub DbGetLine { + my ($dbh, $query) = @_; + my $prep = $dbh->prepare( $query ); + my $exec = $prep->execute() or die $DBI::errstr; + + if ($exec < 0){ + print $DBI::errstr; + } + + while (my @rows = $prep->fetchrow_array()) { + my $row = $rows[0]; + return $row; + } +} + sub LoginOk { - my ($dbh, $username, $pass, $userid, - $magic_cookie, $uid_cookie, - $cookiesdir) = @_; + my ($dbh, $username, $pass, $userid, $magic_cookie, $uid_cookie, $cookiesdir) = @_; my $loginsuccess = PasswdLogin($dbh, $username, $pass); if (not defined $loginsuccess){ $loginsuccess = CookieLogin($userid, $magic_cookie, $uid_cookie, $cookiesdir); @@ -142,6 +147,27 @@ sub LoginOk { return $loginsuccess; } +sub ListUsers { + my ($dbh) = shift; + my @userstable = (); + my $prep = $dbh->prepare(q{SELECT name,mail from pigeons;} ); + my $exec = $prep->execute() or die $DBI::errstr; + + if ($exec < 0){ + print $DBI::errstr; + } + + while (my @rows = $prep->fetchrow_array()) { + #print "$rows[0]\t$rows[1]\n"; + push @userstable, + qq{ + $rows[0] + $rows[1] + }; + } + return @userstable; +} + sub CookieLogin { my ($userid, $magic_cookie, $uid_cookie, $cookiesdir) = @_; if (not $userid =~ /^([0-9]+)$/){ @@ -163,10 +189,10 @@ sub CookieLogin { my $login_cookiefile = "$cookiesdir/$userid/$filename.txt"; if (-e $login_cookiefile){ - open my $in, '<', $login_cookiefile or die "[CookieLogin function] can't read file: $!"; + open my $in, '<', $login_cookiefile or die "can't read file: $!"; $rip_line = readline $in; - $ua_line = readline $in; - $id_line = readline $in; + $ua_line = readline $in; + $id_line = readline $in; $uid_line = readline $in; close $in; chomp ($rip_line, $ua_line, $id_line); # chomp the \n @@ -175,15 +201,15 @@ sub CookieLogin { return; } - my %id_line_cookie = CGI::Cookie->parse($id_line); + my %id_line_cookie = CGI::Cookie->parse($id_line); my %uid_line_cookie = CGI::Cookie->parse($uid_line); - my $id_value = $id_line_cookie{'id'}->value; - my $uid_value = $uid_line_cookie{'uid'}->value; + my $id_value = $id_line_cookie{'id'}->value; + my $uid_value = $uid_line_cookie{'uid'}->value; - my $ip_match = $rip_line cmp $rIP; - my $ua_match = $ua_line cmp $uagent; - my $uid_match = $uid_cookie->value cmp $uid_value; - my $id_match = $magic_cookie->value cmp $id_value; + my $ip_match = $rip_line cmp $rIP; + my $ua_match = $ua_line cmp $uagent; + my $uid_match = $uid_cookie->value cmp $uid_value; + my $id_match = $magic_cookie->value cmp $id_value; if ($ip_match == 0 and $ua_match == 0 and $uid_match == 0 and $id_match == 0){ return $userid; @@ -205,11 +231,11 @@ sub PasswdLogin { } } my ($hash, $userid) = undef; - my $selecthash = qq{SELECT pass from users where mail='$username' or name='$username';}; + my $selecthash = qq{SELECT pass from pigeons where mail='$username' or name='$username';}; $hash = DbGetLine($dbh, $selecthash); if (defined $hash and length($hash) > 1){ if(argon2id_verify($hash,$pass)){ - my $selectuserid = qq{SELECT userid from users where pass='$hash';}; + my $selectuserid = qq{SELECT userid from pigeons where pass='$hash';}; $userid = DbGetLine($dbh, $selectuserid); if ($userid =~ /^([0-9]+)$/){ $userid = $1; @@ -280,7 +306,52 @@ sub UntaintCGIFilename { return $filename; } -my $hostname = $ENV{'SERVER_NAME'}; +sub GetRFC822Date { + # https://stackoverflow.com/a/40149475, Daniel VÃrità + use POSIX qw(strftime locale_h); + my $old_locale = setlocale(LC_TIME, "C"); + my $date = strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time())); + setlocale(LC_TIME, $old_locale); + return $date; +} + +sub SendGpigeonMail { + my ($recipient, $title, $message) = @_; + use Net::SMTP; + use Net::SMTPS; + use MIME::Entity; + my $rfc822date = GetRFC822Date() or die; + my $HAS_MAILSERVER = 0; + my $mailsender = q{sender_addr_goes_here}; + my $mailsender_smtp = q{smtp_domain_goes_here}; + my $mailsender_port = q{smtp_port_goes_here}; + my $mailsender_pw = q{sender_pw_goes_here}; + my $smtp = undef; + if ($HAS_MAILSERVER){ + $smtp = Net::SMTP->new(Host => 'localhost') or die; + } + else { + $smtp = Net::SMTPS->new($mailsender_smtp, Port => $mailsender_port, doSSL => 'ssl', Debug_SSL => 0); + $smtp->auth($mailsender, $mailsender_pw) or die; + } + my $notifylinkbymail_data = MIME::Entity->build( + Date => $rfc822date, + From => $mailsender, + To => $recipient, + Charset => 'utf-8', + Subject => $title, + Data => [$message]) or die; + $smtp->mail($mailsender) or die "Net::SMTP module has broke: $!."; + if ($smtp->to($recipient)){ + $smtp->data($notifylinkbymail_data->stringify); + $smtp->dataend(); + $smtp->quit(); + } + else { + die $smtp->message(); + } +} + my $db_path = q{db_path_goes_here}; my $cookiesdir = q{cookies_dir_goes_here}; @@ -291,10 +362,11 @@ my $cgi_query_get = CGI->new; my $username = $cgi_query_get->param('username'); my $pass = $cgi_query_get->param('password'); my $disconnect = $cgi_query_get->param('disconnect'); +my $adminpanselect = $cgi_query_get->param('adminpan'); my ( $checkedornot, $hidden_loginfield, $magic_cookie, $uid_cookie, $idval, $refresh_form, $userid) = undef; -my $linkgen_notif = my $mailisok_notif = my $deletion_notif = my $login_notif = ''; +my $linkgen_notif = my $sentmail_notif = my $mailisok_notif = my $deletion_notif = my $login_notif = my $adminpan_field = my $adminbtn = ''; my @created_links = (); my %cur_cookies = CGI::Cookie->fetch; $uid_cookie = $cur_cookies{'uid'}; @@ -311,16 +383,17 @@ if (not defined $magic_cookie){ # cookie is not set $hidden_loginfield = qq{}; $refresh_form = qq{
- $hidden_loginfield - $adminpan_field - -
}; -}else{ + $hidden_loginfield + $adminpan_field + + }; +} +else{ $hidden_loginfield = qq{}; $refresh_form = qq{
- $adminpan_field - -
}; + $adminpan_field + + }; $idval = $magic_cookie->value; if ($idval =~ /^([\w]+)$/){ $idval = $1; @@ -334,35 +407,34 @@ if (not defined $magic_cookie){ # cookie is not set } if ($disconnect and defined $magic_cookie){ # if we disconnect and cookie is active - my $delete_id_cookie = CGI::Cookie->new( - -name => 'id', - -value => $idval, - -expires => '-1d', - '-max-age' => '-1d', - -domain => ".$hostname", - -path => '/', - -secure => 1, - -httponly => 1, - -samesite => 'Strict', - ); - my $delete_uid_cookie = CGI::Cookie->new( - -name => 'uid', - -value => $userid, - -expires => '-1d', - '-max-age' => '-1d', - -domain => ".$hostname", - -path => '/', - -secure => 1, - -httponly => 1, - -samesite => 'Strict', - ); - my $f = "$cookiesdir/$userid/$idval.txt"; - if (-e "$f"){ - unlink "$f" or die "cant delete cookie at $f :$!\n"; # delet it - - } - print "Set-Cookie: $delete_uid_cookie\n"; - print "Set-Cookie: $delete_id_cookie\n"; + my $delete_id_cookie = CGI::Cookie->new( + -name => 'id', + -value => $idval, + -expires => '-1d', + '-max-age' => '-1d', + -domain => ".$hostname", + -path => '/', + -secure => 1, + -httponly => 1, + -samesite => 'Strict', + ); + my $delete_uid_cookie = CGI::Cookie->new( + -name => 'uid', + -value => $userid, + -expires => '-1d', + '-max-age' => '-1d', + -domain => ".$hostname", + -path => '/', + -secure => 1, + -httponly => 1, + -samesite => 'Strict', + ); + my $f = "$cookiesdir/$userid/$idval.txt"; + if (-e "$f"){ + unlink "$f" or die "cant delete cookie at $f :$!\n"; # delet it + } + print "Set-Cookie: $delete_uid_cookie\n"; + print "Set-Cookie: $delete_id_cookie\n"; } @@ -372,17 +444,17 @@ print "Cache-Control: no-store, must-revalidate\n"; if($loginok){ $userid = $loginok; - my $user_mailaddr = DbGetLine($dbh, qq{SELECT mail from users where userid='$userid';}); - my $nick = DbGetLine($dbh, qq{SELECT name from users where userid='$userid';}); - my $isadmin = DbGetLine($dbh, qq{SELECT isadmin from users where userid='$userid';}); + my $user_mailaddr = DbGetLine($dbh, qq{SELECT mail from pigeons where userid='$userid';}); + my $nick = DbGetLine($dbh, qq{SELECT name from pigeons where userid='$userid';}); + my $isadmin = DbGetLine($dbh, qq{SELECT isadmin from pigeons where userid='$userid';}); LoginCookieGen($userid, $magic_cookie, $cookiesdir); if ($isadmin){ $adminbtn = qq{
- $hidden_loginfield - - -
}; + $hidden_loginfield + + + }; if (not -d "i/$userid"){ mkpath("./i/$userid"); } @@ -394,36 +466,35 @@ if($loginok){ if (defined $cgi_query_get->param('supprlien')){ my $pending_deletion = $cgi_query_get->param('supprlien'); - - #make sure a form file deletion POST request don't go deleting other things + #make sure smart and malicious users don't go deleting other things if ($pending_deletion =~ /^l\/$userid\/([\w]+)\.cgi$/ or $pending_deletion =~ /^i\/$userid\/([\w]+)\.cgi$/) { if (unlink UntaintCGIFilename($pending_deletion)){ - $deletion_notif = qq{$text_strings{link_del_ok}}; + $deletion_notif=qq{$text_strings{link_del_ok}}; } else { - $deletion_notif = qq{$text_strings{link_del_failed} $pending_deletion: $!}; + $deletion_notif=qq{$text_strings{link_del_failed} $pending_deletion: $!}; } } } if (defined $cgi_query_get->param('supprtout')){ rmtree("./l/$userid", {keep_root=>1, safe=>1}); - $deletion_notif = qq{$text_strings{link_del_ok}}; + $deletion_notif=qq{$text_strings{link_del_ok}}; } if (defined $cgi_query_get->param('delallinvites')){ rmtree("./i/$userid", {keep_root=>1, safe=>1}); - $deletion_notif = qq{$text_strings{link_del_ok}}; + $deletion_notif=qq{$text_strings{link_del_ok}}; } if (defined $cgi_query_get->param('geninv')){ - my $invite_asker = scalar $cgi_query_get->param('opt-mail'); - $mailisok_notif = qq{$text_strings{addr} $invite_asker $text_strings{addr_nok}}; - my $str_rand_obj = String::Random->new; - my $random_fn = $str_rand_obj->randregex('\w{64}'); - my $NEW_FORM_FILENAME = "$random_fn.cgi"; - my $HREF_LINK = "https://$hostname/cgi-bin/i/$userid/$NEW_FORM_FILENAME"; - my $INVITES_PATH = "./i/$userid/$NEW_FORM_FILENAME"; + my $invite_asker = scalar $cgi_query_get->param('opt-mail'); + $mailisok_notif = qq{$text_strings{addr} $invite_asker $text_strings{addr_nok}}; + my $str_rand_obj = String::Random->new; + my $random_fn = $str_rand_obj->randregex('\w{64}'); + my $GENERATED_FORM_FILENAME = "$random_fn.cgi"; + my $HREF_LINK = "https://$hostname/cgi-bin/i/$userid/$GENERATED_FORM_FILENAME"; + my $INVITES_PATH = "./i/$userid/$GENERATED_FORM_FILENAME"; open my $in, '<', $invites_template_path or die "Can't read link template file: $!"; open my $out, '>', $INVITES_PATH or die "Can't write to link file: $!"; @@ -433,10 +504,10 @@ if($loginok){ s/mail = undef;/mail = q{$invite_asker};/g; s/{mailfield_goes_here}/{}/g; } - s/{mailfield_goes_here}/{}/g; + s/{mailfield_goes_here}/{}/g; if (defined $cgi_query_get->param('mailnotif') ){ - s/EMAIL_NOTIF = .*/EMAIL_NOTIF = q{1}/g + s/EMAIL_NOTIF = .*/EMAIL_NOTIF = q{1};/g } if (defined $cgi_query_get->param('adminprom') ){ @@ -445,7 +516,6 @@ if($loginok){ else{ s/is_admin_goes_here/0/g } - s/{user_mailaddr_goes_here}/{$user_mailaddr}/g; print $out $_; } @@ -455,18 +525,21 @@ if($loginok){ close $out or die; $linkgen_notif = qq{$text_strings{link_generated_ok}:
$HREF_LINK
}; + if (defined $cgi_query_get->param('invitemail') and Email::Valid->address($invite_asker)){ + SendGpigeonMail($invite_asker,"[GPIGEON](Do not reply) You have been invited to $hostname","Greetings,\n\n\tYou have been invited to create an GPIGEON account on $hostname.\n\tClick on the link below to fill in the form:\n\t$HREF_LINK\n\tIf you believe this mail is not meant for you, ignore it and mail the webmaster or admin\@les-miquelots.net about it.\n\nKind regards,\nGpigeon mailing system at $hostname.") or $sentmail_notif = "$!"; + } } if (defined $cgi_query_get->param('mail')){ my $link_asker = scalar $cgi_query_get->param('mail'); if ( Email::Valid->address($link_asker) ){ - $mailisok_notif = qq{$text_strings{addr} $link_asker $text_strings{addr_ok}}; - my $str_rand_obj = String::Random->new; - my $random_fn = $str_rand_obj->randregex('\w{64}'); - my $NEW_FORM_FILENAME = "$random_fn.cgi"; - my $HREF_LINK = "https://$hostname/cgi-bin/l/$userid/$NEW_FORM_FILENAME"; - my $LINK_PATH = "./l/$userid/$NEW_FORM_FILENAME"; + $mailisok_notif = qq{$text_strings{addr} $link_asker $text_strings{addr_ok}}; + my $str_rand_obj = String::Random->new; + my $random_fn = $str_rand_obj->randregex('\w{64}'); + my $GENERATED_FORM_FILENAME = "$random_fn.cgi"; + my $HREF_LINK = "https://$hostname/cgi-bin/l/$userid/$GENERATED_FORM_FILENAME"; + my $LINK_PATH = "./l/$userid/$GENERATED_FORM_FILENAME"; open my $in, '<', $link_template_path or die "Can't read link template file: $!"; open my $out, '>', $LINK_PATH or die "Can't write to link file: $!"; @@ -479,63 +552,76 @@ if($loginok){ chmod(0755,$LINK_PATH) or die; close $out or die; - $linkgen_notif = qq{$text_strings{link_generated_ok}:
$HREF_LINK
}; + $linkgen_notif = qq{$text_strings{link_generated_ok}:
$HREF_LINK
}; + if (defined $cgi_query_get->param('notiflinkbymail')){ + SendGpigeonMail($link_asker,"[GPIGEON](Do not reply) Your encrypted form is ready","Greetings,\n\n\tAn encrypted form has been generated for you on $hostname.\n\tClick on the link below to fill in the form:\n\t$HREF_LINK\n\tIf you believe this mail is not meant for you, ignore it and mail the webmaster or admin\@les-miquelots.net about it.\n\nKind regards,\nGpigeon mailing system at $hostname.") or $sentmail_notif="$!" ; + } } else{ $mailisok_notif = qq{$text_strings{addr} $link_asker $text_strings{addr_nok}}; } } - my @links_table = GetFileTable("l/$userid", $hidden_loginfield, $adminpan_field); + my @links_table = GetFileTable("l/$userid", $hidden_loginfield, $adminpan_field); print 'Content-type: text/html',"\n\n"; if ($adminpanselect and $isadmin){ - my @invites_table = GetFileTable("i/$userid", $hidden_loginfield, $adminpan_field); - - - print qq{ - - - - - - - $text_strings{web_title} - - -

GPIGEON - Admin panel

+ my @invites_table = GetFileTable("i/$userid", $hidden_loginfield, $adminpan_field); + + + print qq{ + + + + + + + $text_strings{web_title} + + +

GPIGEON - Admin panel

Welcome to the admin panel. Here, you can view and generate account invites and also search and delete users.

-
- $hidden_loginfield - -
-
- - -
- $refresh_form +
+ $hidden_loginfield + +
+
+ + +
+ $refresh_form
$hidden_loginfield $adminpan_field -