Initial commit

This commit is contained in:
Thomas Schwery 2010-08-14 22:34:11 +02:00
commit eb48e9e8d7
2 changed files with 310 additions and 0 deletions

149
Privileges/Drop.pm Normal file
View file

@ -0,0 +1,149 @@
package Privileges::Drop;
use strict;
use warnings;
use English;
use Carp;
our $VERSION = '1.01';
=head1 NAME
Privileges::Drop - A module to make it simple to drop all privileges, even
POSIX groups.
=head1 DESCRIPTION
This module tries to simplify the process of dropping privileges. This can be
useful when your Perl program needs to bind to privileged ports, etc. This
module is much like Proc::UID, except that it's implemented in pure Perl.
=head1 SYNOPSIS
use Privileges::Drop;
# Do privileged stuff
# Drops privileges and sets euid/uid to 1000 and egid/gid to 1000.
drop_uidgid(1000, 1000);
# Drop privileges to user nobody looking up gid and uid with getpwname
# This also set the enviroment variables USER, LOGNAME, HOME and SHELL.
drop_privileges('nobody');
=head1 METHODS
=over
=cut
use base "Exporter";
our @EXPORT = qw(drop_privileges drop_uidgid);
=item drop_uidgid($uid, $gid, @groups)
Drops privileges and sets euid/uid to $uid and egid/gid to $gid.
Supplementary groups can be set in @groups.
=cut
sub drop_uidgid {
my ($uid, $gid, @reqPosixGroups) = @_;
# Sort the groups and make sure they are uniq
my %groupHash = map { $_ => 1 } ($gid, @reqPosixGroups);
my $newgid ="$gid ".join(" ", sort { $a <=> $b } (keys %groupHash));
# Drop privileges to $uid and $gid for both effective and save uid/gid
$GID = $EGID = $newgid;
$UID = $EUID = $uid;
# Sort the output so we can compare it
my %GIDHash = map { $_ => 1 } ($gid, split(/\s/, $GID));
my $cgid = int($GID)." ".join(" ", sort { $a <=> $b } (keys %GIDHash));
my %EGIDHash = map { $_ => 1 } ($gid, split(/\s/, $EGID));
my $cegid = int($EGID)." ".join(" ", sort { $a <=> $b } (keys %EGIDHash));
# Check that we did actually drop the privileges
if($UID ne $uid or $EUID ne $uid or $cgid ne $newgid or $cegid ne $newgid) {
croak("Could not drop privileges to uid:$uid, gid:$newgid\n"
."Currently is: UID:$UID, EUID=$EUID, GID=$cgid, EGID=$cegid\n");
}
}
=item drop_privileges($user)
Drops privileges to the $user, looking up gid and uid with getpwname and
calling drop_uidgid() with these arguments.
The environment variables USER, LOGNAME, HOME and SHELL are also set to the
values returned by getpwname.
Returns the $uid and $gid on success and dies on error.
NOTE: If drop_privileges() is called when you don't have root privileges
it will just return undef;
=cut
sub drop_privileges {
my ($user) = @_;
croak "No user give" if !defined $user;
# Check if we are root and stop if we are not.
if($UID != 0 and $EUID != 0) {
return;
}
# Find user in passwd file
my ($uid, $gid, $home, $shell) = (getpwnam($user))[2,3,7,8];
if(!defined $uid or !defined $gid) {
croak("Could not find uid and gid user $user");
}
# Find all the groups the user is a member of
my @groups;
while (my ($name, $comment, $ggid, $mstr) = getgrent()) {
my %membership = map { $_ => 1 } split(/\s/, $mstr);
if(exists $membership{$user}) {
push(@groups, $ggid) if $ggid ne 0;
}
}
# Cleanup $ENV{}
$ENV{USER} = $user;
$ENV{LOGNAME} = $user;
$ENV{HOME} = $home;
$ENV{SHELL} = $shell;
drop_uidgid($uid, $gid, @groups);
return ($uid, $gid, @groups);
}
=back
=head1 NOTES
As this module only uses Perl's build in function, it relies on them to work
correctly. That means setting $GID and $EGID should also call setgroups(),
something that might not have been the case before Perl 5.004. So if you are
running an older version, Proc::UID might be a better choice.
=head1 AUTHOR
Troels Liebe Bentsen <tlb@rapanden.dk>
=head1 COPYRIGHT
Copyright(C) 2007-2009 Troels Liebe Bentsen
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=cut
1;

161
gopher.pl Normal file
View file

@ -0,0 +1,161 @@
#!/usr/bin/perl
#####################################
# Thomas Schwery <thomas@schwery.me>
####################################
#
# TODO:
# - Support for a list of tags
# - Cleanup on ctrl-c
# - Multithreading
# - ...
use strict;
use IO::Socket;
use Fcntl ':mode';
use Privileges::Drop;
use HTML::Parse;
use HTML::FormatText;
use Text::Wrap;
use Date::Parse;
use Date::Format;
our $port = 70;
our %entries = ();
$SIG{INT} = \&signal_handler_interrupt;
my $socket = new IO::Socket::INET (LocalPort => $port, Proto => 'tcp', Listen => '1');
die "Unable to open port\n" unless $socket;
drop_privileges('nobody');
while (1) {
my $listen_socket = $socket->accept();
$_ = <$listen_socket>;
s/\r*\n*//g;
s/\t.*//g; # Don't care about the parameters now ...
my $directory = $_;
print "Client " . $.. ": <" . $_ . ">\n";
if (-f $directory) {
if (substr($directory, 0, 4) eq "blog") {
$listen_socket->send(send_blog_entry($directory));
} else {
open FILE, $directory;
while (<FILE>) {
$listen_socket->send($_);
}
$listen_socket->send("\r\n.\r\n") if (-T $directory);
}
} else {
my $header = generate_header($directory);
my $blog_entries = list_blog_entries();
$listen_socket->send($header . "\r\n");
$listen_socket->send("\r\n");
$listen_socket->send("i----- Blog entries ---------------\tfake\tfake\t0\r\n");
$listen_socket->send($blog_entries . "\r\n");
$listen_socket->send("\r\n.\r\n");
}
}
sub generate_header {
my ($directory) = @_;
$directory .= "/" if ($directory);
return "\r\n" unless (-T $directory . "header");
my $message = "";
open FILE, $directory . "header";
while (<FILE>) {
$message .= "i" . $_ . "\tfake\tfake\t0\r\n";
}
close FILE;
$message .= "0About this gopher server\theader\tlocalhost\t" . $port . "\r\n";
return $message;
}
sub map_content {
my ($dir_entry, $directory) = @_;
$directory .= "/" if ($directory);
my $file_type = -f $directory . $dir_entry ? 0 : 1;
$file_type = 9 if (-f $directory . $dir_entry && -B $directory . $dir_entry);
return $file_type . $dir_entry . "\t" . $directory . $dir_entry . "\tlocalhost\t" . $port;
}
sub filter_content {
my ($dir_entry, $blog_dir) = @_;
return 1 unless (substr($dir_entry,0,1) eq "." || $dir_entry eq "header");
return 0;
}
sub list_blog_entries {
my $blog_dir = "blog";
unless (opendir(IMD, $blog_dir)) {
die("Fuck you all ...");
}
my @files = readdir(IMD);
closedir(IMD);
@files = grep filter_content($_, $blog_dir), @files;
@files = map map_blog_entry($_, $blog_dir), @files;
@files = sort sort_entry_line($a,$b), @files;
return join("\r\n", @files);
}
sub sort_entry_line {
my $a_date = $2 if ($a =~ m/^0(\[.*\]\s)?(.*?)\s*-.*$/);
my $b_date = $2 if ($b =~ m/^0(\[.*\]\s)?(.*?)\s*-.*$/);
return str2time($a_date) <=> str2time($b_date);
}
sub read_entry_file {
my ($entry_file) = @_;
my $subject = "";
my $date_file = "";
my $tags = "";
open ENTRY, $entry_file;
while (<ENTRY>) {
s/\r*\n*//;
$subject = $1 if (m/^Subject:\s*(.*)$/);
$tags = $1 if (m/^Tags:\s*(.*)$/);
$date_file = $1 if (m/^Date:\s*(.*)$/);
}
close ENTRY;
my $time = str2time($date_file);
my $date = time2str("%o %h %Y", $time);
return "[" . $tags . "] " . $date . " - " . $subject if($tags);
return $date. " - " . $subject;
}
sub map_blog_entry {
my ($entry_file, $entry_dir) = @_;
my $entry_title = read_entry_file($entry_dir . "/" . $entry_file);
return "0" . $entry_title . "\t" . $entry_dir . "/" . $entry_file . "\tlocalhost\t" . $port;
}
sub send_blog_entry {
my ($entry_file) = @_;
open FILE, $entry_file;
my $entry_text = "";
while (<FILE>) {
$entry_text .= $_;
}
$entry_text =~ s/\r?\n/<br>/mg;
my $plain_text = HTML::FormatText->new->format(parse_html($entry_text));
$Text::Wrap::columns = 80;
$plain_text = wrap("\t", "", $plain_text);
return $plain_text;
}
sub signal_handler_interrupt {
print "ctrl-c, exiting...\n";
exit 0;
}