Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

Anonymous User Add For Linux Shell

by lacertus (Monk)
on Mar 29, 2003 at 03:08 UTC ( [id://246575]=sourcecode: print w/replies, xml ) Need Help??
Category: Utility Scripts
Author/Contact Info Lacertus
Description: I am 'presiding madman' of a Chicago based LUG, and I thought it might be apropos to allow users who've no experience with a *nix shell to be able to create their own account on the public webserver. Essentially, I have created a password protect 'newuser' account, whose information I give out upon a member's registration so they can log in. The password file has no 'shell' per se for this 'newuser' accnt; rather, this script is the shell. Of course, you must be quite careful with something like this, and while I have addressed all the security issues that come to mind, I'm sure this script isn't vulnerability free. Feel free to contact me with questions/suggestions/patches (if yer real cool ;)

The script allows for a newuser to create a username and assign a password of their choice; what's more, it logs all newusers, emails, etc, and also emails all this info to the administrator, so you can keep appraised of what's going down. Enjoy!

Ciao for Now,
Lacertus
#!/usr/bin/perl -T
######################################################################
+#####
##    Created by Pararox
##    Rolled on 3/15/03
##    Perl 5.8.0, Slackware Linux
##
##    downUnder.pl
##    VERSION 1.03.00
######################################################################
+#####
$ENV{PATH}='';

use strict;

use Email::Valid;
use Term::ReadKey;
use Mail::Send;

# use constant LOG_FILE => "/home/newuser/downUnder.log";

my ($i,            # general iterative value
$valid,            # basic true/false for input validity checking
$make_account,        # same as comply really, will merge
$comply,        # hold answer to yes/no questions
$username,        # user request for username
$full_name,        # user's full name
$first_name,        # user's first name
$email,            # user's email address
$uid,            # user's uid (next in '100' group on this system)
$max,            # for use finding max currently held UID
$passwd,        # user's request for a password
$passwd2,        # for password consistency checking
$date);            # precise time at which user added
my $gid=100;        # change to suit your system's needs
my $shell='/bin/bash';    # user's shell, this is all I have on my box

system ("/usr/bin/clear");
print "\nWelcome to the official server of the IIT Linux User's Group\
+n\n";


######################################################################
+#####
##    This could probably be removed.  It's just making sure the user
##    is ready to proceede with account creation.
######################################################################
+#####
$i=0;
do {
    print "Not an option!\n" if $i>0;
    print "Are you going to make a shell account now? (y/n): ";
    chomp($make_account = <STDIN>);
    ++$i;
    die "\nCiao!\n" if ($make_account eq 'n');
} while ($make_account !~ /^\s*y/i);    # while input isn't 'Y|y'

system ("/usr/bin/clear");

print <<END_OF_MSG;
Ok here's the gig. I'm making this service, and everything
associated with a shell account, freely available for your
use, to do with as you please. It's my pleasure, and I
hope you learn a whole lot.

With this, comes responsibility on your part to use these
tools in accordance with US laws and general morality.
Simply, don't go hacking away at someone else's box, or
do anything that will wind me up in jail. I might add as
a side note that I log *everything* plus some, so don't do
anything over the network that will make me suspicious.

Also understand that, while this is my primary server,
there are unexpected periods of downtime (generally
attributed to the crappy network here at the school,
and the strange electricity problems we have every so
often). I promise absolute security with regard to your
data; I can't promise complete integrity of the disks.
There are occassional occurances of data loss, due to
my mucking about, or some other such cause.  Just bear
this in mind.
END_OF_MSG


######################################################################
+#####
##    Making sure user complies with system rules; this one is importa
+nt
##    and I think that the above legaliase should be modified to be
##    more specific, but for now, it will do
######################################################################
+#####
$i=0;
do {
    print "Not an option!\n" if $i>0;
    print "\nI fully understand/comply and respect the system (y/n): "
+;
    chomp($comply=<STDIN>);
    ++$i;
    die "\nCiao!\n" if (($comply eq 'n') || ($comply eq 'N'));
} while ($comply !~ /\s*y/i);    # while input isn't 'Y|y'


######################################################################
+#####
##    Start of the *main* do loop; this loop goes for quite a while
##    and only drops into the final stages of the program once the use
+r
##    has reviewed inputs and agreed to creating the account with the
##    given information.
######################################################################
+#####
do {
    system ("/usr/bin/clear");
    print "\nOf the following, NOTHING will be public but your usernam
+e\n\n";


    ##################################################################
+#
    ##    Getting user's fullname, for system records
    ##################################################################
+#
    $i=0;
    do {
        print "\nYour full name is required!\n" if $i>0;
        print "(*) Your actual full name: ";
        chomp($full_name = <STDIN>);
        ++$i;
    } while ($full_name !~ /^\s*(\D+)\s+\D+\b/); # while not 2 distinc
+t
                             # non-space boundaries
    $first_name = $1;    # for personalized output :)


    ##################################################################
+#
    ## Getting user's current email address for system records only.
    ## Using the Email::Valid mod to make sure the format is valid and
    ## also that the domain name is valid (yes it runs a DNS query)
    ##################################################################
+#
    $i=0;
    $valid=1;    # setting to *invalid* status to begin with
    do {
        print "\nYour *valid* email address is required!\n" if $i>0;
        print "(*) Your email address: ";
        chomp($email = <STDIN>);
        ++$i;
        # -address (checks for validity), -mxcheck (DNS check)
        Email::Valid->address( -address=>$email, -mxcheck=>1)
            ? ($valid=0) : ($valid=1);
    } while ($valid); # while Email::Valid comes up as invalid


    ##################################################################
    ##    Asking for and getting user's requested username for the
    ##    system.  Opening the passwd file in order to ensure user
    ##    name requested isn't already taken.
    ##################################################################
    $i=0;
    print "\nThe following items are CASE SENSITIVE!\n";
    do {
        if ($i>0) {print "\nYour username is required!\n";}
        elsif ($valid==1) {print "\nThat username is taken!\n";} 
        print "(*) Your desired username: ";
        chomp($username = <STDIN>);
        ++$i;
        $valid=0;
        open PASSWD, "</etc/passwd"
            or die "Something has gone wrong: $!\n";
        while (<PASSWD>) {
            /#*(\S+):x/;    # getting usernames from file
            if ($username eq $1) {$valid=1; $i=0}; #invalid if match
        }
        close PASSWD;
    } while (($username !~ /^\s*\S+\s*$/) || ($valid==1)); #while blan
+k


    ##################################################################
+###
    ##     Getting the user's desired password, which thru a regex mus
+t
    ##    be an alphanumeric between 6 and 100 characters in length.
    ##    Using Term::ReadKey to switch stty -echo (which doesn't seem
    ##    to work itself (stty that is), maybe because of do-while?
    ##################################################################
+###
    $i=0;
    do {
        print "\n\nCommon!  A *real* password, now (6 or more alphanum
+erics)!\n" if ($i>0); 
        print "\n\nPasswords don't match!\n" if ($valid != 0);
        ++$i;
        $valid=0;
        ReadMode 2; # Term:ReadKey (cooked mode,echo off)
        print "(*) Your account password (won't echo): ";
        chomp($passwd = <STDIN>);
        print "\n(!) Retype that password to make certain: ";
        chomp($passwd2 = <STDIN>);
        ReadMode 0; # Term:ReadKey (restore original settings)
        if ($passwd ne $passwd2) {
            $valid=1;
            $i=0;
        }
    } while ( ($passwd !~ /^\s*\S{6,100}\s*$/) || $valid);
    $passwd = crypt($passwd, time()); # encrypting for shadow, time() 
+seeded


######################################################################
+####
##    Outputting what the user has given me, making sure all is well.
##    If user doesn't like, start from beginning (I'd like to implemen
+t
##    function calls for all of these input sections so that user
##    can specify one particular area that needs to be amended, but fo
+r
##    now it'll suffice
######################################################################
+####
print "\n\n----------------------------------------------------------\
+n" .
    "Ok, $first_name, here is how you'll be entered into the system:\n
+" .
    "Name:     $full_name\n" .
    "Email:    $email\n" .
    "Username: $username\n" .
    "---------------------------------------------------------\n\n";

    $i=0;    
    do {
        print "\nChoose a valid option to continue!\n" if ($i>0);
        print "Is this correct?  Now is your last chance to bail! (y/n
+): ";
        chomp($comply=<STDIN>);
        ++$i;
    } while ($comply !~ /^\s*(y|n)\s*$/i);

} while ($comply !~ /^\s*y\s*$/i);


######################################################################
+######
##    Now we are checking for the next available UID on the system; no
+w
##    on this system UID's start at 100 for users, but this number may
##    not be very portable across platforms, should put a DEF at begin
+ning
######################################################################
+######
$max=100;
open PASSWD, "/etc/passwd";
while (<PASSWD>) {
    chomp;
    if (/^#*\S+:x:(1\d\d):/) {
        $max=$1 if ($max<$1);
    }
}
close PASSWD;
$uid=($max+1);


######################################################################
+###
##    Actually making the system call to useradd, this is it baby!
######################################################################
+###
system(
    '/usr/bin/sudo',
    '/usr/sbin/useradd',
    '-u'=> $uid,
    '-s'=> $shell,
    '-p'=> $passwd,
    '-g'=> $gid,
    '-m'=> $username
);
chomp($date=localtime);    # setting for log output


######################################################################
+######
##    Sending logged information to system administrator and putting i
+nfo
##    into a *hardcoded* log file within the 'newuser' ~
######################################################################
+######
my $msg = new Mail::Send;
$msg = new Mail::Send (
    Subject    =>'New User Added!',
    To    =>'root');
my $fh = $msg->open;
print $fh "Attention Administrator!\n\n" .
    "A new user has recently been added.  Information follows:\n\n" .
    "Date: " . $date .
    "\nUsername: " . $username . " (UID: " . $uid . ")" .
    "\nUser: " . $full_name .
    "\nEmail: " . $email;
$fh->close;

open Log, ">>/home/newuser/downUnder.log"
    or warn "Problem writing to log file ($!)";
print Log "$username :: $full_name :: $email - $date\n";
close Log;

######################################################################
+######
##    Terminating message
######################################################################
+######
system("/usr/bin/clear");
print <<END_OF_MSG;

OK, your brand spanking new account is ready to roll.

You will want to open a secure shell (ssh) connection
via either protocol 1 or 2 to this host and then use
your newly created username and password to log into
the system.

Enjoy!  And please always remember the following golden
rules, and all will be well:

#1) Respect the privacy of others.
#2) Respect the security and integrity of this machine.
#3) Think before you type.

Love and Linux!

(You will be automagically disconnected in 30 seconds)
END_OF_MSG

sleep 30;
0;
Replies are listed 'Best First'.
Re: Anonymous User Add For Linux Shell
by Aristotle (Chancellor) on Mar 31, 2003 at 02:20 UTC

    You don't need to and shouldn't fiddle with /etc/passwd - useradd will simply fail if a user with that name already exists. So you should simply attempt to create that user and check if you succeeded - that way, even multiple concurrent copies of the script won't be able to step on each other's toes.

    Don't try to find a UID yourself either. According to man useradd:

    The default is to use the smallest ID value greater than 99 and greater than every other user. Values between 0 and 99 are typically reserved for system accounts.

    That's exactly what you want anyway.

    Also, you have tons of duplicate code in there. All these $i = 0; do { ... } while $foo; loops are very similar - factor out the code! Do It Once And Only Once.

    Lastly, it would make the code much easier to read if you shuffle the prompts and text out of the code's way. As a bonus, if you give the prompts and messages names, the code becomes self documenting and you can get rid of all of that commentary as well.

    #!/usr/bin/perl -w use strict; no warnings 'once'; use vars qw(%MSG %PROMPT); use Email::Valid; use Term::ReadKey; use Mail::Send; $ENV{PATH}=''; $|++; sub message { my $clear = shift; my $message = shift; system "/usr/bin/clear" if $clear; printf "\n$message", @_; } sub ask_user { my ($prompt, $check) = @_; local $_; { print "\n$prompt->[0]"; chomp($_ = <STDIN>); print("\n$prompt->[1]\n"), redo unless $check->(); } return $_; } eval do { local $/; <DATA> }; # read messages and prompts use constant GID => 100; use constant SHELL => '/bin/bash'; use constant LOG_FILE => "/home/newuser/downUnder.log"; my ($USERNAME, $FULL_NAME, $FIRST_NAME, $EMAIL, $PASSWD); message(1, $MSG{BANNER}); ask_user($PROMPT{MAKE_ACCOUNT}, sub { die "\nCiao!\n" if 'n' eq lc; return 1 if 'y' eq lc; return; }); message(1, $MSG{TOS}); ask_user($PROMPT{COMPLY}, sub { die "\nCiao!\n" if 'n' eq lc; return 1 if 'y' eq lc; return; }); { message(1, $MSG{COLLECTDETAILS}); $FULL_NAME = ask_user($PROMPT{FULL_NAME}, sub { /^\s*(\D+)\s+\D+\b/ ? ($FIRST_NAME = $1, return 1) : 0 }); $EMAIL = ask_user($PROMPT{EMAIL}, sub { Email::Valid->address(-address => $_, -mxcheck => 1) }); message(0, $MSG{CASESENSITIVE}); ReadMode 2; # cooked mode,echo off { $PASSWD = ask_user($PROMPT{PASSWD}, sub { length > 6 }); my $passwd2 = ask_user($PROMPT{CONFIRM}, sub { 1 }); print("\n\nYou typed two different passwords!\n"), redo if $PASSWD ne $passwd2; } ReadMode 0; # restore original settings message(0, $MSG{SUMMARY}, $FIRST_NAME, $FULL_NAME, $EMAIL); redo if 'n' eq lc ask_user($PROMPT{CORRECT}, sub { /^(y|n)$/i }); } $USERNAME = ask_user($PROMPT{USERNAME}, sub { return unless /\A\w+\z/; system( '/usr/bin/sudo', '/usr/sbin/useradd', '-s' => SHELL, '-g' => GID, '-p' => crypt($PASSWD, time()), '-c' => $FULL_NAME, '-m' => $_, ) and return; return 1; }); my $date = localtime; printf( { Mail::Send->new( Subject => 'New User Added!', To => 'root' )->open } $MSG{ADMINMAIL}, $date, $USERNAME, scalar getpwnam($USERNAME), $FULL_NAME, $EMAIL ); { open my $fh, ">>", LOG_FILE or warn "Failed opening log file: $!\n"; print $fh "$USERNAME :: $FULL_NAME :: $EMAIL - $date\n"; } message(1, $MSG{THANKSBYE}); sleep 30; __END__ %PROMPT = ( MAKE_ACCOUNT => [ "Are you going to make a shell account now? (y/n): ", "Not an option", ], COMPLY => [ "I fully understand/comply and respect the system (y/n): ", "Not an option", ], FULL_NAME => [ "(*) Your actual full name: ", "Full name is required!", ], EMAIL => [ "(*) Your email address: ", "Your *valid* email address is required!", ], PASSWD => [ "(*) Your account password (won't echo): ", "Your password has to be longer than 6 characters.", ], CONFIRM => [ "(*) Confirm your password by retyping it: ", "", ], CORRECT => [ "Now is your last chance to abort. Is this data correct? (y/n) +: ", "Not an option", ], USERNAME => [ "Please choose a username: ", "That username is invalid or taken.", ], ); %MSG = ( BANNER => <<"EOT", Welcome to the official server of the IIT Linux User's Group EOT TOS => <<"EOT", Ok here's the gig. I'm making this service, and everything associated with a shell account, freely available for your use, to do with as you please. It's my pleasure, and I hope you learn a whole lot. With this, comes responsibility on your part to use these tools in accordance with US laws and general morality. Simply, don't go hacking away at someone else's box, or do anything that will wind me up in jail. I might add as a side note that I log *everything* plus some, so don't do anything over the network that will make me suspicious. Also understand that, while this is my primary server, there are unexpected periods of downtime (generally attributed to the crappy network here at the school, and the strange electricity problems we have every so often). I promise absolute security with regard to your data; I can't promise complete integrity of the disks. There are occassional occurances of data loss, due to my mucking about, or some other such cause. Just bear this in mind. EOT COLLECTDETAILS => <<"EOT", Nothing of the following will be public. EOT CASESENSITIVE => <<"EOT", Note: The following items are case sensitive. EOT SUMMARY => <<"EOT", Ok, %s: Your name is %s. Your email is %s. EOT THANKSBYE => <<"EOT", OK, your brand spanking new account is ready to roll. You will want to open a secure shell (ssh) connection via either protocol 1 or 2 to this host and then use your newly created username and password to log into the system. Enjoy! And please always remember the following golden rules, and all will be well: #1) Respect the privacy of others. #2) Respect the security and integrity of this machine. #3) Think before you type. Love and Linux! (You will be automagically disconnected in 30 seconds) EOT ADMINMAIL => <<"EOT", Attention Administrator! A new user has recently been added. Information follows: Date: %s Username: %s (UID: %s) User: %s Email: %s EOT );

    Makeshifts last the longest.

Re: Anonymous User Add For Linux Shell
by graff (Chancellor) on Mar 31, 2003 at 00:04 UTC
    If it's possible that two or more people may be creating their own accounts at about the same time, shouldn't there be some sort of semaphore or other locking mechanism that encloses the "get next uid" and the "sudo useradd"? Given that you're reading /etc/passwd one line at a time, it's conceivable that two processes could read the same version of that file and both try to create an account with the same value of $uid.

    For example, below is a module I adapted for my own use, based on an old Perl Journal article; with that module, you can just add a couple lines around the sensitive part:

    use Semfile; ... my $lock = Semfile->new("/tmp/getuid.lock") or die "oops! $!"; # do your "get next uid" and your "sudo useradd" here $lock->release;
    Here's the module (apologizes for the silly name):
    package Semfile; use strict; use FileHandle; use Carp; # copied (and documented) by Dave Graff from # The Perl Journal, issue #23 (Vol.6 No.1): # "Resource Locking with Semaphore Files" # by Sean M. Burke. # (http://www.samag.com/documents/s=4075/sam1013019385270/sam0203i.htm +) # Create and manage semaphore files, which will guarantee that # simultaneous processes competing for a single resource do not # collide when using that resource. # The semaphore (locked) file has no content -- it is simply the # thing to check, and lock if it's available, before using/altering # the actual shared resource. sub new { my $class = shift(@_); my $filespec = shift(@_) or Carp::croak("What filespec?"); my $fh = new FileHandle; $fh->open( ">$filespec" ) or Carp::croak("Can't open semaphore file $filespec: $!"); chmod 0664, $filespec; # make it ug+rw use Fcntl 'LOCK_EX'; flock $fh, LOCK_EX; return bless {'fh' => $fh}, ref($class) || $class; } sub release { undef $_[0]{'fh'}; } 1; # End of module

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: sourcecode [id://246575]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others lurking in the Monastery: (8)
As of 2024-04-18 07:38 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found