C18ANT has asked for the wisdom of the Perl Monks concerning the following question:

Hi all, my first post here and I've read the guidelines on how to post, so here goes! I have a project at work, whereby we have a secure server for transporting documents too large for email. It's running on Ubuntu server 16.04. The previous iteration was really old and needed replacing but I found out how the customer accounts were created: with a Perl script! It's a really good script and the functions work perfectly for our requirements. I'm sure you'll all be able to read the functions but I'm not too good with Perl, so I can only gather the basic stuff that I can read in English but in a nutshell: we run the command "sudo sftp-create-user $username" to create an account for a customer, the script automatically adds them into a group whereby they can only access their own user folder and the account automatically expires after a specified amount of time (if no time is specified, the default is 7 days) - as shown below. For the record: I have installed the String::MkPasswd module; File::Copy and File::Path seem to be embedded into the installed version of Perl 5.30.0 distribution running on this server. The scripts worked fine on the old server but since moving them and installing Perl and the modules specified in the code, the scripts are only really half working now.
#!/usr/bin/perl # Feature changes: # 1. Rewritten in Perl, using String::MkPasswd # 2. Simplified accounts structure, all accounts now # feature an expiry time, and may not traverse into # other accounts' directories # 3. Changed username format to all lowercase letters, # numbers and underscore # 4. The user purging script actually works! # 5. Better passwords via String::MkPasswd # 6. You can now extend user accounts use strict; use String::MkPasswd qq{mkpasswd}; my $username = lc( shift ); my $expiration = shift; if( $< != 0 ) { print qq{You must run this script as root or sudo!\n}; exit; } if( $username eq q{} ) { print qq{You must enter a username!\n}; exit; } elsif ( defined( getpwnam( $username ) ) ) { print qq{This username already exists!\n}; exit; } elsif ( $username =~ m/\W/ ) { print qq{You may not use special characters in the username!\n}; exit; } mkdir qq{/FTP/$username}; mkdir qq{/FTP/$username/$username}; my $password = mkpasswd(); if ( $password =~ m/\"|\'/ ) { $password = mkpasswd(); } system( qq{useradd -s /bin/false -M $username} ); system( qq{usermod -g customers $username} ); system( qq{usermod -d /$username $username} ); system( qq{chown -R $username:customers /FTP/$username/$username} ); print qq{Created SFTP user $username\n}; system( qq{echo "$username:$password" | chpasswd} ); print qq{Assigned password $password to $username\n}; open( my $expFile , qq{>>/etc/ssh/sftp-users.dat} ); my $currentTime = time; my $newTime; if( defined( $expiration ) ) { $newTime = ( $expiration * 86400 ) + $currentTime; print qq{Set expiration to $expiration days.\n}; } else { $newTime = $currentTime + 604800; print qq{Set expiration to 7 days (default).\n}; } print $expFile qq{$username,$newTime\n}; close( $expFile );
As far as I can tell, it works fine in the creation of the groups, the user account and the folders in /FTP/$username. However, if you can spot anything to improve on, please do point it out! Anyway, back to the issue... The account doesn't seem to be expiring (i.e. automatically deleting itself and the user folder in /FTP/$username/ and removing itself from any system groups) and when I run the backup command "sudo sftp-delete-user $username" (code below) it seems to successfully delete the user account, as it doesn't show up in "/etc/passwd" but the user folders in /FTP/$username/ all still remain. As per the "how to post a question effectively" page, laziness is a virtue for many people in the vast umbrella of I.T. and programming - if it's not already obvious, I'm a system administrator/network engineer for a cyber security company and this being a manual process creates additional overhead that I simply don't have the time or the will to do manually, so any help would be greatly appreciated!
#!/usr/bin/perl # Feature changes: # 1. Rewritten in Perl, using String::MkPasswd # 2. Simplified accounts structure, all accounts now # feature an expiry time, and may not traverse into # other accounts' directories # 3. Changed username format to all lowercase letters, # numbers and underscore # 4. The user purging script actually works! # 5. Better passwords via String::MkPasswd # 6. You can now extend user accounts use strict; use File::Copy; use File::Path; my $username = lc( shift ); if( $< != 0 ) { print qq{You must run this script as root or sudo!\n}; exit; } open( my $oldExpFile, qq{</etc/ssh/sftp-users.dat} ) || die qq{Couldn't open expirations file!\n}; open( my $newExpFile, qq{>/etc/ssh/sftp-users.dat.tmp} ) || die qq{Couldn't create new expirations file!\n}; while( my $line = <$oldExpFile> ) { ( my $userInLine ) = $line =~ /^(.+?),.+?$/; if( $username eq $userInLine ) { system( qq{deluser $username} ); system( qq{delgroup $username} ); rmtree( qq{/FTP/$username} ); print qq{Deleted $username!\n}; } else { print $newExpFile $line; } } close( $oldExpFile ); close( $newExpFile ); move( qq{/etc/ssh/sftp-users.dat.tmp}, qq{/etc/ssh/sftp-users.dat} );
And the code for the command "sudo sftp-purge-users" also doesn't seem to work. This script is almost identical to the previous "sftp-delete-user" with the only difference being that line 18 is removed.

Thanks,
Anthony

Replies are listed 'Best First'.
Re: Script for SFTP users not deleting accounts automatically
by bliako (Abbot) on Jul 05, 2019 at 10:27 UTC

    This does not do what you want, what happens if mkpasswd() yields illegal characters for a second time in a row?:

    my $password = mkpasswd(); if ( $password =~ m/\"|\'/ ) { $password = mkpasswd(); }

    consider this instead:

    my $password; do { # theoretically it can get stuck in an infinite loop here... $password = mkpasswd(); } while( $password =~ m/\"|\'/ );

    As for deleting user dirs, you really should check rmtree's return code, see File::Path . Also check each system()'s return code and handle failures accordingly. You don't do this.

    For a discussion about using system() and alternatives see Calling External Commands More Safely

      I second these points! Here's one more: open( my $expFile , qq{>>/etc/ssh/sftp-users.dat} ); would be better as the three-argument open and it should definitely check for errors: open( my $expFile, '>>', q{/etc/ssh/sftp-users.dat} ) or die $!;

Re: Script for SFTP users not deleting accounts automatically
by Don Coyote (Hermit) on Jul 07, 2019 at 08:07 UTC

    As Data coming into the script I'd be inclined to validate the username beyond lower-casing. Also I have included character class checking as usually usernames are not allowed to have a number (and possibly underscore too, would have to check that) as the first character. The \W would allow that as the first character.

    Performing the untainting prior to calling system validation functions may also prevent badly constructed usernames getting passed through to system in functions such as getpwnam

    my $un = shift; unless( $un ~= s/\A([_A-Za-z][_A-Za-z0-9]+)\Z/$1/ ){ # Log/Warn(L4/5); #? exit &ask_user_to_resubmit_request; } $un = lc($un); ... unless($<){..}

    I'd likely suggest restricting user removal solely to root, but that is probably fine as long as admin priveleges are in place.

    update:splitting the uid check from the user retrieval may even be better. We first see if administrator is calling the script before pulling in and untainting the user supplied data, then call the process body.

    #uid check unless($< == 0){ # Log attempt and exit; }else{ print "Sue! How do you do?\nWe've been rooting for you" } #untaint and foldcase user data my $un = shift; unless( $un ~= s/\A([A-Za-z]\W+)\Z/$1/ ){ # Log attempt exit &ask_user_to_resubmit_request; } $un = lc($un); #carry on if(defined(getpwnam $un)){ .. }