mirror of
https://github.com/webmin/webmin.git
synced 2025-07-20 16:48:46 +00:00
596 lines
17 KiB
Perl
Executable File
596 lines
17 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
# userpermissions_form.cgi
|
|
# Display a the list of users and their permissions
|
|
# Author: Mattias Gaertner
|
|
#
|
|
# Abstract:
|
|
# - Allows editing the user permissions for a directory with an
|
|
# .ftpaccess file.
|
|
# - It has a select field to easily add a user to the .ftpaccess file.
|
|
# - Shows a list of users with their permissions.
|
|
# - Provides minimum allowed commands (at the moment hardcoded in
|
|
# $MiniumCommands).
|
|
# These commands will applied to any new and changed permissions.
|
|
# - Shows names instead of the hard to remember FTP abbreviations
|
|
# (e.g. PBSZ).
|
|
# - Commands can be combined. For example: RNFR and RNTO are shown
|
|
# as only one permission.
|
|
# - adds automatically a DenyAll All limit, so the default is to allow
|
|
# nothing.
|
|
#
|
|
# ToDos:
|
|
# - multi language support
|
|
# - a page to config the minimum commands
|
|
# - a page to config the tuples (combined commands)
|
|
# - Probably some functions already exists in webmin and can be replaced
|
|
|
|
require './proftpd-lib.pl';
|
|
|
|
&ReadParse();
|
|
|
|
# read .ftpaccess file
|
|
$file = $in{'file'};
|
|
$title = &text('ftpindex_header', "<tt>$in{'file'}</tt>");
|
|
$return = "ftpaccess_index.cgi";
|
|
$rmsg = $text{'ftpindex_return'};
|
|
|
|
&ui_print_header($title, "Edit User Permissions", "",
|
|
undef, undef, undef, undef, &restart_button());
|
|
|
|
#########################################
|
|
# Navigation parameters
|
|
foreach $h ('virt', 'idx', 'file', 'limit', 'anon', 'global') {
|
|
if (defined($in{$h})) {
|
|
$NavigationData.="<input type=hidden name=$h value='$in{$h}'>\n";
|
|
push(@args, "$h=$in{$h}");
|
|
}
|
|
}
|
|
$args = join('&', @args);
|
|
|
|
|
|
# These are the FTP Commands, that any user have
|
|
$MinimumCommands="CWD XCWD CDUP XCUP PORT PASS PASV EPRT EPSV"
|
|
." PWD XPWD SIZE HELP NOOP AUTH ABORT USER LIST TYPE PROT QUIT PBSZ MDTM MODE";
|
|
|
|
$Commands{"CWD"}="Change working directory";
|
|
$Commands{"XCWD"}="";
|
|
$Commands{"CDUP"}="";
|
|
$Commands{"XCUP"}="";
|
|
$Commands{"PORT"}="";
|
|
$Commands{"PASV"}="enter passive mode";
|
|
$Commands{"EPRT"}="";
|
|
$Commands{"EPSV"}="";
|
|
$Commands{"RNFR"}="Rename From";
|
|
$Commands{"RNTO"}="Rename To";
|
|
$Commands{"DELE"}="Delete File";
|
|
$Commands{"RMD"}="Remove Directory";
|
|
$Commands{"XRMD"}="X Remove Directory";
|
|
$Commands{"MKD"}="Create Directory";
|
|
$Commands{"XMKD"}="X Create Directory";
|
|
$Commands{"MODE"}="";
|
|
$Commands{"PWD"}="";
|
|
$Commands{"XPWD"}="";
|
|
$Commands{"SIZE"}="";
|
|
$Commands{"SITE_CHMOD"}="Change Unix File Permissions";
|
|
$Commands{"STAT"}="Return Server Status";
|
|
$Commands{"SYST"}="Prints System Info";
|
|
$Commands{"HELP"}="";
|
|
$Commands{"NOOP"}="";
|
|
$Commands{"AUTH"}="";
|
|
$Commands{"PBSZ"}="";
|
|
$Commands{"PROT"}="";
|
|
$Commands{"TYPE"}="Set Transfer Type";
|
|
$Commands{"MODE"}="Set Transfer Mode";
|
|
$Commands{"MDTM"}="List Modification Time";
|
|
$Commands{"RETR"}="Retrieve (Read)";
|
|
$Commands{"STOR"}="Store (Write)";
|
|
$Commands{"STOU"}="Store Unique";
|
|
$Commands{"APPE"}="Append";
|
|
$Commands{"REST"}="Restart Write";
|
|
$Commands{"ABOR"}="Abort";
|
|
$Commands{"USER"}="";
|
|
$Commands{"PASS"}="";
|
|
$Commands{"LIST"}="List remote files";
|
|
$Commands{"QUIT"}="";
|
|
$Commands{"TupleRMD"} = "Remove Directory";
|
|
$Commands{"TupleMKD"} = "Make Directory";
|
|
$Commands{"TupleRN"} = "Rename";
|
|
$Commands{"TuplePWD"} = "Print Working Directory";
|
|
|
|
# Not implemented by proftpd:
|
|
#$Commands{"STRU"}="Specify File Structure";
|
|
|
|
# Here you can group commands
|
|
$CommandTuples{"TupleRMD"} = "RMD XRMD";
|
|
$CommandTuples{"TupleMKD"} = "MKD XMKD";
|
|
$CommandTuples{"TupleRN"} = "RNFR RNTO";
|
|
$CommandTuples{"TuplePWD"} = "PWD XPWD";
|
|
|
|
# Create CommandToTuple array
|
|
foreach $TupleName(sort keys %CommandTuples){
|
|
foreach $Command(split (" ",$CommandTuples{$TupleName})){
|
|
next unless ($Command);
|
|
$CommandToTuple{$Command}=$TupleName;
|
|
}
|
|
}
|
|
|
|
|
|
#########################################
|
|
# Get user list and read old permissions
|
|
&GetUsers();
|
|
&GetFTPAccessUserPerms($file);
|
|
|
|
#########################################
|
|
# Parse Input and update .ftpaccess file
|
|
|
|
foreach $ParamName(keys %in){
|
|
#print "Name=\"$ParamName\" Value=\"".$in{$ParamName}."\"<br>\n";
|
|
if($ParamName eq "AddUser"){
|
|
$Username=$in{$ParamName};
|
|
if($Username =~ /^[a-zA-Z0-9_]+$/){
|
|
&AddUser($Username,$file);
|
|
}
|
|
}
|
|
if($ParamName eq "DeleteUser"){
|
|
$Username=$in{$ParamName};
|
|
if($Username =~ /^[a-zA-Z0-9_]+$/){
|
|
if($in{"Confirm Delete User"} eq "on"){
|
|
&DeleteUser($Username,$file);
|
|
#print "New used usernames: $UsedUsernames<br>\n";
|
|
} else {
|
|
print "<H2>To really delete a user, please check the confim checkbox.</H2>\n";
|
|
}
|
|
}
|
|
}
|
|
if($ParamName eq "ChangePermissions"){
|
|
$Username=$in{$ParamName};
|
|
if($Username =~ /^[a-zA-Z0-9_]+$/){
|
|
&ChangePermissions($Username,$file);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#########################################
|
|
# select box and button for add user
|
|
print "<form action=userpermissions_form.cgi method=get>\n";
|
|
print $NavigationData;
|
|
print "<H3>Add an User to the permission table</H3>\n";
|
|
print "<select name=\"AddUser\">\n";
|
|
foreach $Username (sort split(" ",$Usernames)){
|
|
print "<option value=\"$Username\">$Username</option>\n";
|
|
}
|
|
print "</select>\n";
|
|
print "<input type=submit value=\"Add User\"><br>\n";
|
|
print "</form>\n";
|
|
|
|
#########################################
|
|
# Print Permissions
|
|
|
|
$MaxColumns=4;
|
|
foreach $Username(sort split (" ",$UsedUsernames)){
|
|
#print "User: $Username Allowed=\"".$UserAllowedCommands{$Username}."\" Denied=\"".$UserDeniedCommands{$Username}."\"\n";
|
|
print "<form action=userpermissions_form.cgi method=get>\n";
|
|
print $NavigationData;
|
|
print "<input type=hidden name=\"ChangePermissions\" value=\"".$Username."\">\n";
|
|
print "<HR WIDTH=\"100%\">\n";
|
|
print "<H2>User: $Username</H2>\n";
|
|
print "<table border=1>\n";
|
|
$Column=0;
|
|
$Row=0;
|
|
foreach $Command(sort keys %Commands){
|
|
if($MinimumCommands =~ /$Command/i){
|
|
# skip minimum permissions, that all users are allowed to
|
|
next;
|
|
}
|
|
if($CommandToTuple{$Command}){
|
|
# skip commands that belong to a tuple
|
|
next;
|
|
}
|
|
$FTPCommands=$Command;
|
|
if($CommandTuples{$FTPCommands}){
|
|
$FTPCommands = $CommandTuples{$FTPCommands};
|
|
}
|
|
|
|
|
|
if($Column == 0){
|
|
if($Row==0){
|
|
print " <tr>\n";
|
|
for ($i=0; $i<$MaxColumns; $i++){
|
|
print " <td>Command</td><td>Allow/Deny/Default</td>\n";
|
|
}
|
|
print " </tr>\n";
|
|
}
|
|
print " <tr>\n";
|
|
}
|
|
$CommandDesc=$Commands{$Command};
|
|
if(!$CommandDesc){
|
|
$CommandDesc = $Command;
|
|
}
|
|
print " <td>$CommandDesc</td><td>\n";
|
|
if(&CommandContains($UserAllowedCommands{$Username},$FTPCommands)){
|
|
$AllowChecked=" checked";
|
|
} else {
|
|
$AllowChecked="";
|
|
}
|
|
if(&CommandContains($UserDeniedCommands{$Username},$FTPCommands)){
|
|
$DenyChecked=" checked";
|
|
} else {
|
|
$DenyChecked="";
|
|
}
|
|
if($AllowChecked || $DenyChecked){
|
|
$DefaultChecked = "";
|
|
} else {
|
|
$DefaultChecked = " checked";
|
|
}
|
|
print " <input type=\"radio\" name=\"".$Command."\" value=\"allow\"".$AllowChecked.">\n";
|
|
print " <input type=\"radio\" name=\"".$Command."\" value=\"deny\"".$DenyChecked.">\n";
|
|
print " <input type=\"radio\" name=\"".$Command."\" value=\"default\"".$DefaultChecked.">\n";
|
|
print " </td>";
|
|
$Column++;
|
|
if($Column == $MaxColumns){
|
|
print " </tr>\n";
|
|
$Column=0;
|
|
$Row++;
|
|
}
|
|
}
|
|
if($Column > 0){
|
|
print " </tr>\n";
|
|
}
|
|
print "</table>\n";
|
|
print "<input type=submit value=\"Change Permissions\">\n";
|
|
print "</form><br>\n";
|
|
|
|
print "<form action=userpermissions_form.cgi method=get>\n";
|
|
print $NavigationData;
|
|
print "<input type=hidden name=\"DeleteUser\" value=\"".$Username."\">\n";
|
|
print "<input type=submit value=\"Delete User Permissions\">\n";
|
|
print "<input type=checkbox name=\"Confirm Delete User\">I'm sure<br>\n";
|
|
print "</form>\n";
|
|
}
|
|
|
|
|
|
#########################################
|
|
# print textarea
|
|
|
|
print "<HR WIDTH=100%>\n";
|
|
print &text('manual_header', "<tt>$file</tt>"),"<p>\n";
|
|
|
|
print "<form action=manual_save.cgi method=post enctype=multipart/form-data>\n";
|
|
print $NavigationData;
|
|
|
|
print "<br><textarea rows=15 cols=80 name=directives>\n";
|
|
$lref = &read_file_lines($file);
|
|
if (!defined($start)) {
|
|
$start = 0;
|
|
$end = @$lref - 1;
|
|
}
|
|
for($i=$start; $i<=$end; $i++) {
|
|
print &html_escape($lref->[$i]),"\n";
|
|
}
|
|
print "</textarea><br><input type=submit value=\"$text{'save'}\"></form>\n";
|
|
|
|
#########################################
|
|
# print footer
|
|
|
|
&ui_print_footer("$return?$args", $rmsg);
|
|
|
|
exit;
|
|
|
|
#########################################################
|
|
|
|
sub GetUsers(){
|
|
my $UserCount=0;
|
|
setpwent();
|
|
while(my @uinfo = getpwent()) {
|
|
if ($uinfo[2] > 100) {
|
|
$UserCount++;
|
|
$Users[$UserCount]=$uinfo[0];
|
|
$Usernames.=" ".$uinfo[0];
|
|
}
|
|
}
|
|
endpwent();
|
|
}
|
|
|
|
sub GetFTPAccessUserPerms(){
|
|
# Fills global variables:
|
|
# $UsedUsernames, %UserAllowedCommands, %UserDeniedCommands
|
|
|
|
my ($FTPAccessFile) = @_;
|
|
|
|
##################################################
|
|
# Read .ftpaccess file
|
|
my $Commands = "";
|
|
|
|
open FTPACCESS, "$FTPAccessFile" or &error("Can't open $FTPAccessFile: $!");
|
|
while (my $line=<FTPACCESS>){
|
|
chomp $line;
|
|
#print $line."\n";
|
|
if($line =~ /<Limit (.*)>/i){
|
|
$Commands = $1;
|
|
#print "Limit $Commands\n";
|
|
}
|
|
if($line =~ /<\/Limit(.*)>/i){
|
|
$Commands = "";
|
|
#print "End Limit $Commands\n";
|
|
}
|
|
if($Commands){
|
|
#print "$line\n";
|
|
if($line =~ /AllowUser (.+)/i){
|
|
my $AllowedUsernames = $1;
|
|
#print "AllowUser $AllowedUsernames\n";
|
|
foreach $AllowedUsername (split (" ",$AllowedUsernames)){
|
|
next unless ($AllowedUsername);
|
|
$UserAllowedCommands{$AllowedUsername}.=" ".$Commands;
|
|
#print "AllowUser $AllowedUsername\n";
|
|
}
|
|
}
|
|
if($line =~ /DenyUser (.+)/i){
|
|
my $DeniedUsernames = $1;
|
|
foreach $DeniedUsername (split (" ",$DeniedUsernames)){
|
|
next unless ($DeniedUsername);
|
|
$UserDeniedCommands{$DeniedUsername}.=" ".$Commands;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
close FTPACCESS;
|
|
|
|
##################################################
|
|
# collect all mentioned users in table
|
|
$UsedUsernames="";
|
|
foreach $Username(keys %UserAllowedCommands){
|
|
#print "Adding $Username\n";
|
|
$UserAllowedCommands{$Username}=
|
|
&UnifyAndExpandCommands($UserAllowedCommands{$Username}." ".$Commands);
|
|
if($UsedUsernames !~ /\b$Username\b/){
|
|
$UsedUsernames.=$Username." ";
|
|
}
|
|
}
|
|
foreach $Username(keys %UserDeniedCommands){
|
|
$UserDeniedCommands{$Username}=
|
|
&UnifyAndExpandCommands($UserDeniedCommands{$Username}." ".$Commands);
|
|
if($UsedUsernames !~ /\b$Username\b/){
|
|
$UsedUsernames.=$Username." ";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub UnifyAndExpandCommands(){
|
|
(my $Commands) = @_;
|
|
my $NewCommands = "";
|
|
foreach $Command(split(" ",$Commands)){
|
|
next unless($Command);
|
|
if($CommandTuples{$Command}){
|
|
$NewCommands.=" ".$CommandTuples{$Command};
|
|
} else {
|
|
$NewCommands.=" ".$Command;
|
|
}
|
|
}
|
|
return &UnifyCommands($NewCommands);
|
|
}
|
|
|
|
sub UnifyCommands(){
|
|
(my $Commands) = @_;
|
|
my $NewCommands = "";
|
|
foreach $Command(split(" ",$Commands)){
|
|
next unless($Command);
|
|
next if($NewCommands =~ /\b$Command\b/i);
|
|
if($NewCommands){
|
|
$NewCommands.=" ";
|
|
}
|
|
$NewCommands.=$Command;
|
|
}
|
|
return $NewCommands;
|
|
}
|
|
|
|
sub AddUser(){
|
|
(my $Username, $FTPAccessFile) = @_;
|
|
|
|
if($Usernames =~ /\b$Usernames\b/){
|
|
print "<H2>Username $Username does not exist.</H2>\n";
|
|
return;
|
|
}
|
|
|
|
if ($UserAllowedCommands{$Username} || $UserDeniedCommands{$Username}){
|
|
# user already exists
|
|
print "<H2>Username $Username already exists.</H2>\n";
|
|
return;
|
|
}
|
|
$UserAllowedCommands{$Username}=$MinimumCommands;
|
|
$UserDeniedCommands{$Username}="";
|
|
if($UsedUsernames !~ /\b$Username\b/){
|
|
$UsedUsernames.=$Username." ";
|
|
}
|
|
|
|
&WritePermissions($FTPAccessFile);
|
|
}
|
|
|
|
sub DeleteUser(){
|
|
(my $Username, $FTPAccessFile) = @_;
|
|
|
|
if($UsedUsernames =~ /\b$Usernames\b/){
|
|
print "<H2>Username $Username does not exist in table.</H2>\n";
|
|
return;
|
|
}
|
|
|
|
if ((!$UserAllowedCommands{$Username}) && (!$UserDeniedCommands{$Username})){
|
|
# user already deleted
|
|
print "<H2>Username $Username is already not in table.</H2>\n";
|
|
return;
|
|
}
|
|
$UserAllowedCommands{$Username}="";
|
|
$UserDeniedCommands{$Username}="";
|
|
$UsedUsernames =~ s/\b$Username\b *//;
|
|
|
|
&WritePermissions($FTPAccessFile);
|
|
}
|
|
|
|
sub ChangePermissions(){
|
|
(my $Username, $FTPAccessFile) = @_;
|
|
|
|
if($UsedUsernames =~ /\b$Usernames\b/){
|
|
print "<H2>Username $Username does not exist in table.</H2>\n";
|
|
return;
|
|
}
|
|
|
|
foreach $Command(keys %Commands){
|
|
#print "$Command value=".$in{$Command}."<br>\n";
|
|
|
|
if($CommandToTuple{$Command}){
|
|
# skip commands in tuples
|
|
next;
|
|
}
|
|
|
|
my $FTPCommands=$Command;
|
|
if($CommandTuples{$FTPCommands}){
|
|
$FTPCommands = $CommandTuples{$FTPCommands};
|
|
}
|
|
|
|
if ($in{$Command} eq "allow"){
|
|
$UserAllowedCommands{$Username}.=" ".$FTPCommands;
|
|
#print "Allow $Username $Command<br>\n";
|
|
} else {
|
|
$UserAllowedCommands{$Username} =
|
|
&RemoveCommands($UserAllowedCommands{$Username},$FTPCommands);
|
|
}
|
|
if ($in{$Command} eq "deny"){
|
|
$UserDeniedCommands{$Username}.=" ".$FTPCommands;
|
|
#print "Deny $Username $Command<br>\n";
|
|
} else {
|
|
$UserDeniedCommands{$Username} =
|
|
&RemoveCommands($UserDeniedCommands{$Username},$FTPCommands);
|
|
}
|
|
}
|
|
$UserAllowedCommands{$Username}=
|
|
&UnifyCommands($MinimumCommands." ".$UserAllowedCommands{$Username});
|
|
$UserDeniedCommands{$Username}=
|
|
&UnifyCommands($UserDeniedCommands{$Username});
|
|
|
|
&WritePermissions($FTPAccessFile);
|
|
}
|
|
|
|
sub WritePermissions(){
|
|
# Read .ftpaccess file, remove all user command permissions
|
|
# and add new set of user permissions
|
|
(my $FTPAccessFile) = @_;
|
|
my $NewConfig = "";
|
|
my $OldCommands = "";
|
|
my $Username;
|
|
|
|
# Lock .ftpaccess file
|
|
&lock_file($FTPAccessFile);
|
|
&lock_file($FTPAccessFile);
|
|
|
|
|
|
# Read old .ftpaccess file
|
|
open FTPACCESS, "$FTPAccessFile" or die "Can't read $FTPAccessFile: $!";
|
|
$DenyAllBlockFound = 0;
|
|
while(my $line = <FTPACCESS>){
|
|
my $ShortLine = $line;
|
|
chomp $ShortLine;
|
|
#print $ShortLine."\n";
|
|
if($ShortLine =~ /<Limit (.*)>/i){
|
|
# start of Limit block
|
|
$OldCommands = $1;
|
|
#print "Limit $OldCommands\n";
|
|
$LimitBlock = $line;
|
|
$ImportantLimitLineFound = 0;
|
|
$DenyAllFound = 0;
|
|
} elsif($ShortLine =~ /<\/Limit(.*)>/i){
|
|
# end of Limit block
|
|
#print "End Limit $OldCommands\n";
|
|
$LimitBlock .= $line;
|
|
if($ImportantLimitLineFound){
|
|
$NewConfig .= $LimitBlock;
|
|
}
|
|
if(($OldCommands =~ /\bALL\b/i) && ($DenyAllFound)){
|
|
# this was a DenyAll for All commands block
|
|
$DenyAllBlockFound = 1;
|
|
}
|
|
$OldCommands = "";
|
|
} elsif($OldCommands){
|
|
#print "$ShortLine\n";
|
|
if($ShortLine =~ /AllowUser (.*)/i){
|
|
# AllowUser line -> will be replaced, not important
|
|
} elsif($ShortLine =~ /DenyUser (.*)/i){
|
|
# DenyUser line -> will be replaced, not important
|
|
} elsif($ShortLine =~ /^ +$/){
|
|
# empty line -> not important, but keep it for readability
|
|
$LimitBlock .= $line;
|
|
} else {
|
|
# other limit directive -> important
|
|
$LimitBlock .= $line;
|
|
$ImportantLimitLineFound = 1;
|
|
if($ShortLine =~ /\bDenyAll\b/i){
|
|
$DenyAllFound = 1;
|
|
}
|
|
}
|
|
} else {
|
|
# other directives -> keep
|
|
$NewConfig .= $line;
|
|
}
|
|
}
|
|
close FTPACCESS;
|
|
|
|
# Append new directives
|
|
|
|
# Append DenyAll block if not already there
|
|
if(!$DenyAllBlockFound){
|
|
$NewConfig.="<Limit All>\n";
|
|
$NewConfig.=" DenyAll\n";
|
|
$NewConfig.="</Limit>\n";
|
|
}
|
|
|
|
# Append Limit blocks for users
|
|
foreach $Username (sort split(" ",$Usernames)){
|
|
my $CurAllow = $UserAllowedCommands{$Username};
|
|
if ($CurAllow){
|
|
$NewConfig.="<Limit ".$CurAllow.">\n";
|
|
$NewConfig.=" AllowUser ".$Username."\n";
|
|
$NewConfig.="</Limit>\n";
|
|
}
|
|
my $CurDeny = $UserDeniedCommands{$Username};
|
|
if ($CurDeny){
|
|
$NewConfig.="<Limit ".$CurDeny.">\n";
|
|
$NewConfig.=" DenyUser ".$Username."\n";
|
|
$NewConfig.="</Limit>\n";
|
|
}
|
|
}
|
|
#print "<br>\n".$NewConfig."<br>\n";
|
|
|
|
# Write new .ftpaccess file
|
|
open FTPACCESS, "> $FTPAccessFile" or die "Can't append to $FTPAccessFile: $!";
|
|
print FTPACCESS $NewConfig;
|
|
close FTPACCESS;
|
|
|
|
# Unlock .ftpaccess file
|
|
&unlock_file($FTPAccessFile);
|
|
|
|
$logtype = 'ftpaccess';
|
|
$logname = $in{'file'};
|
|
&webmin_log($logtype, "user permissions", $logname, \%in);
|
|
}
|
|
|
|
sub CommandContains(){
|
|
(my $Commands, my $SubSet) = @_;
|
|
foreach my $Command(split(" ",$SubSet)){
|
|
next unless($Command);
|
|
if($Commands =~ /\b$Command\b/i){
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
sub RemoveCommands(){
|
|
(my $Commands, my $SubSet) = @_;
|
|
foreach my $Command(split(" ",$SubSet)){
|
|
next unless($Command);
|
|
$Commands =~ s/\b$Command\b *//gi;
|
|
}
|
|
return $Commands;
|
|
}
|
|
|
|
# end.
|