2 # userpermissions_form.cgi
3 # Display a the list of users and their permissions
4 # Author: Mattias Gaertner
7 # - Allows editing the user permissions for a directory with an
9 # - It has a select field to easily add a user to the .ftpaccess file.
10 # - Shows a list of users with their permissions.
11 # - Provides minimum allowed commands (at the moment hardcoded in
13 # These commands will applied to any new and changed permissions.
14 # - Shows names instead of the hard to remember FTP abbreviations
16 # - Commands can be combined. For example: RNFR and RNTO are shown
17 # as only one permission.
18 # - adds automatically a DenyAll All limit, so the default is to allow
22 # - multi language support
23 # - a page to config the minimum commands
24 # - a page to config the tuples (combined commands)
25 # - Probably some functions already exists in webmin and can be replaced
27 require './proftpd-lib.pl';
31 # read .ftpaccess file
33 $title = &text('ftpindex_header', "<tt>$in{'file'}</tt>");
34 $return = "ftpaccess_index.cgi";
35 $rmsg = $text{'ftpindex_return'};
37 &ui_print_header($title, "Edit User Permissions", "",
38 undef, undef, undef, undef, &restart_button());
40 #########################################
41 # Navigation parameters
42 foreach $h ('virt', 'idx', 'file', 'limit', 'anon', 'global') {
43 if (defined($in{$h})) {
44 $NavigationData.="<input type=hidden name=$h value='$in{$h}'>\n";
45 push(@args, "$h=$in{$h}");
48 $args = join('&', @args);
51 # These are the FTP Commands, that any user have
52 $MinimumCommands="CWD XCWD CDUP XCUP PORT PASS PASV EPRT EPSV"
53 ." PWD XPWD SIZE HELP NOOP AUTH ABORT USER LIST TYPE PROT QUIT PBSZ MDTM MODE";
55 $Commands{"CWD"}="Change working directory";
60 $Commands{"PASV"}="enter passive mode";
63 $Commands{"RNFR"}="Rename From";
64 $Commands{"RNTO"}="Rename To";
65 $Commands{"DELE"}="Delete File";
66 $Commands{"RMD"}="Remove Directory";
67 $Commands{"XRMD"}="X Remove Directory";
68 $Commands{"MKD"}="Create Directory";
69 $Commands{"XMKD"}="X Create Directory";
74 $Commands{"SITE_CHMOD"}="Change Unix File Permissions";
75 $Commands{"STAT"}="Return Server Status";
76 $Commands{"SYST"}="Prints System Info";
82 $Commands{"TYPE"}="Set Transfer Type";
83 $Commands{"MODE"}="Set Transfer Mode";
84 $Commands{"MDTM"}="List Modification Time";
85 $Commands{"RETR"}="Retrieve (Read)";
86 $Commands{"STOR"}="Store (Write)";
87 $Commands{"STOU"}="Store Unique";
88 $Commands{"APPE"}="Append";
89 $Commands{"REST"}="Restart Write";
90 $Commands{"ABOR"}="Abort";
93 $Commands{"LIST"}="List remote files";
95 $Commands{"TupleRMD"} = "Remove Directory";
96 $Commands{"TupleMKD"} = "Make Directory";
97 $Commands{"TupleRN"} = "Rename";
98 $Commands{"TuplePWD"} = "Print Working Directory";
100 # Not implemented by proftpd:
101 #$Commands{"STRU"}="Specify File Structure";
103 # Here you can group commands
104 $CommandTuples{"TupleRMD"} = "RMD XRMD";
105 $CommandTuples{"TupleMKD"} = "MKD XMKD";
106 $CommandTuples{"TupleRN"} = "RNFR RNTO";
107 $CommandTuples{"TuplePWD"} = "PWD XPWD";
109 # Create CommandToTuple array
110 foreach $TupleName(sort keys %CommandTuples){
111 foreach $Command(split (" ",$CommandTuples{$TupleName})){
112 next unless ($Command);
113 $CommandToTuple{$Command}=$TupleName;
118 #########################################
119 # Get user list and read old permissions
121 &GetFTPAccessUserPerms($file);
123 #########################################
124 # Parse Input and update .ftpaccess file
126 foreach $ParamName(keys %in){
127 #print "Name=\"$ParamName\" Value=\"".$in{$ParamName}."\"<br>\n";
128 if($ParamName eq "AddUser"){
129 $Username=$in{$ParamName};
130 if($Username =~ /^[a-zA-Z0-9_]+$/){
131 &AddUser($Username,$file);
134 if($ParamName eq "DeleteUser"){
135 $Username=$in{$ParamName};
136 if($Username =~ /^[a-zA-Z0-9_]+$/){
137 if($in{"Confirm Delete User"} eq "on"){
138 &DeleteUser($Username,$file);
139 #print "New used usernames: $UsedUsernames<br>\n";
141 print "<H2>To really delete a user, please check the confim checkbox.</H2>\n";
145 if($ParamName eq "ChangePermissions"){
146 $Username=$in{$ParamName};
147 if($Username =~ /^[a-zA-Z0-9_]+$/){
148 &ChangePermissions($Username,$file);
154 #########################################
155 # select box and button for add user
156 print "<form action=userpermissions_form.cgi method=get>\n";
157 print $NavigationData;
158 print "<H3>Add an User to the permission table</H3>\n";
159 print "<select name=\"AddUser\">\n";
160 foreach $Username (sort split(" ",$Usernames)){
161 print "<option value=\"$Username\">$Username</option>\n";
164 print "<input type=submit value=\"Add User\"><br>\n";
167 #########################################
171 foreach $Username(sort split (" ",$UsedUsernames)){
172 #print "User: $Username Allowed=\"".$UserAllowedCommands{$Username}."\" Denied=\"".$UserDeniedCommands{$Username}."\"\n";
173 print "<form action=userpermissions_form.cgi method=get>\n";
174 print $NavigationData;
175 print "<input type=hidden name=\"ChangePermissions\" value=\"".$Username."\">\n";
176 print "<HR WIDTH=\"100%\">\n";
177 print "<H2>User: $Username</H2>\n";
178 print "<table border=1>\n";
181 foreach $Command(sort keys %Commands){
182 if($MinimumCommands =~ /$Command/i){
183 # skip minimum permissions, that all users are allowed to
186 if($CommandToTuple{$Command}){
187 # skip commands that belong to a tuple
190 $FTPCommands=$Command;
191 if($CommandTuples{$FTPCommands}){
192 $FTPCommands = $CommandTuples{$FTPCommands};
199 for ($i=0; $i<$MaxColumns; $i++){
200 print " <td>Command</td><td>Allow/Deny/Default</td>\n";
206 $CommandDesc=$Commands{$Command};
208 $CommandDesc = $Command;
210 print " <td>$CommandDesc</td><td>\n";
211 if(&CommandContains($UserAllowedCommands{$Username},$FTPCommands)){
212 $AllowChecked=" checked";
216 if(&CommandContains($UserDeniedCommands{$Username},$FTPCommands)){
217 $DenyChecked=" checked";
221 if($AllowChecked || $DenyChecked){
222 $DefaultChecked = "";
224 $DefaultChecked = " checked";
226 print " <input type=\"radio\" name=\"".$Command."\" value=\"allow\"".$AllowChecked.">\n";
227 print " <input type=\"radio\" name=\"".$Command."\" value=\"deny\"".$DenyChecked.">\n";
228 print " <input type=\"radio\" name=\"".$Command."\" value=\"default\"".$DefaultChecked.">\n";
231 if($Column == $MaxColumns){
241 print "<input type=submit value=\"Change Permissions\">\n";
242 print "</form><br>\n";
244 print "<form action=userpermissions_form.cgi method=get>\n";
245 print $NavigationData;
246 print "<input type=hidden name=\"DeleteUser\" value=\"".$Username."\">\n";
247 print "<input type=submit value=\"Delete User Permissions\">\n";
248 print "<input type=checkbox name=\"Confirm Delete User\">I'm sure<br>\n";
253 #########################################
256 print "<HR WIDTH=100%>\n";
257 print &text('manual_header', "<tt>$file</tt>"),"<p>\n";
259 print "<form action=manual_save.cgi method=post enctype=multipart/form-data>\n";
260 print $NavigationData;
262 print "<br><textarea rows=15 cols=80 name=directives>\n";
263 $lref = &read_file_lines($file);
264 if (!defined($start)) {
268 for($i=$start; $i<=$end; $i++) {
269 print &html_escape($lref->[$i]),"\n";
271 print "</textarea><br><input type=submit value=\"$text{'save'}\"></form>\n";
273 #########################################
276 &ui_print_footer("$return?$args", $rmsg);
280 #########################################################
285 while(my @uinfo = getpwent()) {
286 if ($uinfo[2] > 100) {
288 $Users[$UserCount]=$uinfo[0];
289 $Usernames.=" ".$uinfo[0];
295 sub GetFTPAccessUserPerms(){
296 # Fills global variables:
297 # $UsedUsernames, %UserAllowedCommands, %UserDeniedCommands
299 my ($FTPAccessFile) = @_;
301 ##################################################
302 # Read .ftpaccess file
305 open FTPACCESS, "$FTPAccessFile" or &error("Can't open $FTPAccessFile: $!");
306 while (my $line=<FTPACCESS>){
309 if($line =~ /<Limit (.*)>/i){
311 #print "Limit $Commands\n";
313 if($line =~ /<\/Limit(.*)>/i){
315 #print "End Limit $Commands\n";
319 if($line =~ /AllowUser (.+)/i){
320 my $AllowedUsernames = $1;
321 #print "AllowUser $AllowedUsernames\n";
322 foreach $AllowedUsername (split (" ",$AllowedUsernames)){
323 next unless ($AllowedUsername);
324 $UserAllowedCommands{$AllowedUsername}.=" ".$Commands;
325 #print "AllowUser $AllowedUsername\n";
328 if($line =~ /DenyUser (.+)/i){
329 my $DeniedUsernames = $1;
330 foreach $DeniedUsername (split (" ",$DeniedUsernames)){
331 next unless ($DeniedUsername);
332 $UserDeniedCommands{$DeniedUsername}.=" ".$Commands;
339 ##################################################
340 # collect all mentioned users in table
342 foreach $Username(keys %UserAllowedCommands){
343 #print "Adding $Username\n";
344 $UserAllowedCommands{$Username}=
345 &UnifyAndExpandCommands($UserAllowedCommands{$Username}." ".$Commands);
346 if($UsedUsernames !~ /\b$Username\b/){
347 $UsedUsernames.=$Username." ";
350 foreach $Username(keys %UserDeniedCommands){
351 $UserDeniedCommands{$Username}=
352 &UnifyAndExpandCommands($UserDeniedCommands{$Username}." ".$Commands);
353 if($UsedUsernames !~ /\b$Username\b/){
354 $UsedUsernames.=$Username." ";
359 sub UnifyAndExpandCommands(){
361 my $NewCommands = "";
362 foreach $Command(split(" ",$Commands)){
363 next unless($Command);
364 if($CommandTuples{$Command}){
365 $NewCommands.=" ".$CommandTuples{$Command};
367 $NewCommands.=" ".$Command;
370 return &UnifyCommands($NewCommands);
375 my $NewCommands = "";
376 foreach $Command(split(" ",$Commands)){
377 next unless($Command);
378 next if($NewCommands =~ /\b$Command\b/i);
382 $NewCommands.=$Command;
388 (my $Username, $FTPAccessFile) = @_;
390 if($Usernames =~ /\b$Usernames\b/){
391 print "<H2>Username $Username does not exist.</H2>\n";
395 if ($UserAllowedCommands{$Username} || $UserDeniedCommands{$Username}){
396 # user already exists
397 print "<H2>Username $Username already exists.</H2>\n";
400 $UserAllowedCommands{$Username}=$MinimumCommands;
401 $UserDeniedCommands{$Username}="";
402 if($UsedUsernames !~ /\b$Username\b/){
403 $UsedUsernames.=$Username." ";
406 &WritePermissions($FTPAccessFile);
410 (my $Username, $FTPAccessFile) = @_;
412 if($UsedUsernames =~ /\b$Usernames\b/){
413 print "<H2>Username $Username does not exist in table.</H2>\n";
417 if ((!$UserAllowedCommands{$Username}) && (!$UserDeniedCommands{$Username})){
418 # user already deleted
419 print "<H2>Username $Username is already not in table.</H2>\n";
422 $UserAllowedCommands{$Username}="";
423 $UserDeniedCommands{$Username}="";
424 $UsedUsernames =~ s/\b$Username\b *//;
426 &WritePermissions($FTPAccessFile);
429 sub ChangePermissions(){
430 (my $Username, $FTPAccessFile) = @_;
432 if($UsedUsernames =~ /\b$Usernames\b/){
433 print "<H2>Username $Username does not exist in table.</H2>\n";
437 foreach $Command(keys %Commands){
438 #print "$Command value=".$in{$Command}."<br>\n";
440 if($CommandToTuple{$Command}){
441 # skip commands in tuples
445 my $FTPCommands=$Command;
446 if($CommandTuples{$FTPCommands}){
447 $FTPCommands = $CommandTuples{$FTPCommands};
450 if ($in{$Command} eq "allow"){
451 $UserAllowedCommands{$Username}.=" ".$FTPCommands;
452 #print "Allow $Username $Command<br>\n";
454 $UserAllowedCommands{$Username} =
455 &RemoveCommands($UserAllowedCommands{$Username},$FTPCommands);
457 if ($in{$Command} eq "deny"){
458 $UserDeniedCommands{$Username}.=" ".$FTPCommands;
459 #print "Deny $Username $Command<br>\n";
461 $UserDeniedCommands{$Username} =
462 &RemoveCommands($UserDeniedCommands{$Username},$FTPCommands);
465 $UserAllowedCommands{$Username}=
466 &UnifyCommands($MinimumCommands." ".$UserAllowedCommands{$Username});
467 $UserDeniedCommands{$Username}=
468 &UnifyCommands($UserDeniedCommands{$Username});
470 &WritePermissions($FTPAccessFile);
473 sub WritePermissions(){
474 # Read .ftpaccess file, remove all user command permissions
475 # and add new set of user permissions
476 (my $FTPAccessFile) = @_;
478 my $OldCommands = "";
481 # Lock .ftpaccess file
482 &lock_file($FTPAccessFile);
483 &lock_file($FTPAccessFile);
486 # Read old .ftpaccess file
487 open FTPACCESS, "$FTPAccessFile" or die "Can't read $FTPAccessFile: $!";
488 $DenyAllBlockFound = 0;
489 while(my $line = <FTPACCESS>){
490 my $ShortLine = $line;
492 #print $ShortLine."\n";
493 if($ShortLine =~ /<Limit (.*)>/i){
494 # start of Limit block
496 #print "Limit $OldCommands\n";
498 $ImportantLimitLineFound = 0;
500 } elsif($ShortLine =~ /<\/Limit(.*)>/i){
502 #print "End Limit $OldCommands\n";
503 $LimitBlock .= $line;
504 if($ImportantLimitLineFound){
505 $NewConfig .= $LimitBlock;
507 if(($OldCommands =~ /\bALL\b/i) && ($DenyAllFound)){
508 # this was a DenyAll for All commands block
509 $DenyAllBlockFound = 1;
512 } elsif($OldCommands){
513 #print "$ShortLine\n";
514 if($ShortLine =~ /AllowUser (.*)/i){
515 # AllowUser line -> will be replaced, not important
516 } elsif($ShortLine =~ /DenyUser (.*)/i){
517 # DenyUser line -> will be replaced, not important
518 } elsif($ShortLine =~ /^ +$/){
519 # empty line -> not important, but keep it for readability
520 $LimitBlock .= $line;
522 # other limit directive -> important
523 $LimitBlock .= $line;
524 $ImportantLimitLineFound = 1;
525 if($ShortLine =~ /\bDenyAll\b/i){
530 # other directives -> keep
536 # Append new directives
538 # Append DenyAll block if not already there
539 if(!$DenyAllBlockFound){
540 $NewConfig.="<Limit All>\n";
541 $NewConfig.=" DenyAll\n";
542 $NewConfig.="</Limit>\n";
545 # Append Limit blocks for users
546 foreach $Username (sort split(" ",$Usernames)){
547 my $CurAllow = $UserAllowedCommands{$Username};
549 $NewConfig.="<Limit ".$CurAllow.">\n";
550 $NewConfig.=" AllowUser ".$Username."\n";
551 $NewConfig.="</Limit>\n";
553 my $CurDeny = $UserDeniedCommands{$Username};
555 $NewConfig.="<Limit ".$CurDeny.">\n";
556 $NewConfig.=" DenyUser ".$Username."\n";
557 $NewConfig.="</Limit>\n";
560 #print "<br>\n".$NewConfig."<br>\n";
562 # Write new .ftpaccess file
563 open FTPACCESS, "> $FTPAccessFile" or die "Can't append to $FTPAccessFile: $!";
564 print FTPACCESS $NewConfig;
567 # Unlock .ftpaccess file
568 &unlock_file($FTPAccessFile);
570 $logtype = 'ftpaccess';
571 $logname = $in{'file'};
572 &webmin_log($logtype, "user permissions", $logname, \%in);
575 sub CommandContains(){
576 (my $Commands, my $SubSet) = @_;
577 foreach my $Command(split(" ",$SubSet)){
578 next unless($Command);
579 if($Commands =~ /\b$Command\b/i){
586 sub RemoveCommands(){
587 (my $Commands, my $SubSet) = @_;
588 foreach my $Command(split(" ",$SubSet)){
589 next unless($Command);
590 $Commands =~ s/\b$Command\b *//gi;