#!/usr/bin/perl
#
# NOTE: Install Perl5 and reference #!/usr/local/bin/perl if you have an older
# version of FreeBSD
#
# This software is intended for distribution under the same terms and spirit
# as FreeBSD.
#
# This script was originally authored by Dan Howard <dannyman@dannyland.org>
# for EnterAct, LLC, 1998. (http://www.enteract.com/)
#
use Fcntl ':flock';
#use Getopt::Std;
# Global, user-customizeable configs
my $pw_path = '/usr/sbin/pw';
my $pw_conf_path = '/etc/pw.conf';
my $sendmail_path = '/usr/sbin/sendmail';
my $welcome_message_path = '/etc/adduser.message';
my $logfile = '/var/log/enteruser.log';
my $organization = '';
my $domain = `/bin/hostname`;
#my $lockfile = '/var/run/enteruser.LOCK';
### Forward declarations ###
# Main functions
sub enteruser;
sub queueuser;
# Queueuser helpers
sub queueadd;
sub queuelist;
sub queuedel;
sub queuedo;
# Common helpers
sub get_user_data;
sub print_user_data;
sub add_user;
# UI
sub get_fullname;
sub get_password;
sub get_shell;
sub get_username;
#sub get_billing_id;
#sub get_group;
#sub get_referral;
# Generic helpers
sub append_file;
### START PROGRAM ###
if( $< != 0 ) {
print "You must be root to run $0!\n";
exit -1;
}
# Here's an example of how you could use GetOpt to extend functionality, but I
# never was too keen on this example. One possibility is to set various
# defaults for a queueuser operation, or change the path for the welcome
# message.
#
# One possibility that has excited me on occasion has been the idea of setting
# input through the command-line, which could ease the lives of advanced users
# somewhat, but not enough that I've bothered to try.
#
# getopts('c');
# # -c is for curt, meaning no welcome message
# if( $opt_c ) {
# print "Will not send welcome message.\n";
# $welcome_message_path = '';
# }
if( $0 =~ /queueuser/ ) {
&queueuser();
}
else {
&enteruser();
}
### END PROGRAM ###
sub enteruser {
my %newuser;
my $y_or_n;
%newuser = &get_user_data;
print "\nVerify User Information\n";
print "-----------------------\n";
&print_user_data(%newuser);
print "\nIs this okay? (Y/n) ";
$y_or_n = <>;
if( $y_or_n !~ /^n/i ) {
&add_user(%newuser);
}
}
sub queueuser {
my @userqueue;
my $choice;
while(1) {
my $yn;
print<<__MENU;
Please Select Operation
-----------------------
[A]dd a user to the queue
[L]ist users in the queue
[D]elete user from the queue
[P]rocess users in queue
[Q]uit this program
__MENU
print "What would you like to do? ";
$choice = <>;
if( $choice =~ /^a/i ) {
LAME: { # the LAME kludge
push @userqueue, &queueadd;
}
print "Add another? (Y/n) ";
$yn = <>;
goto LAME unless $yn =~ /^n/i;
}
elsif( $choice =~ /^l/i ) {
&queuelist(@userqueue);
}
elsif( $choice =~ /^d/i ) {
@userqueue = &queuedel(@userqueue);
}
elsif( $choice =~ /^p/i ) {
@userqueue = &queuedo(@userqueue);
}
elsif( $choice =~ /^q/i ) {
if( @userqueue ) {
print "There are unprocessed users in your queue.\n";
print "If you quit now, they will be lost!\n";
print "Do you really want to quit? (y/N) ";
$choice = <>;
if( $choice =~ /^y/i ) {
exit(0);
}
}
else {
exit(0);
}
}
else {
print "\nHuh?\n";
}
}
}
# Pretty much just enteruser, only we stay within queueuser
sub queueadd {
my %newuser;
my $y_or_n;
%newuser = &get_user_data;
print "\nVerify User Information\n";
print "-----------------------\n";
&print_user_data(%newuser);
print "\nIs this okay? (Y/n) ";
$y_or_n = <>;
if( $y_or_n !~ /^n/i ) {
return {%newuser};
}
}
sub queuelist {
my @userlist = @_;
my $user;
for $user (@userlist) {
printf("%8s %16s %10s %4s\n",
$user->{username}, $user->{password},
$user->{fullname}, $user->{shell});
}
}
sub queuedel {
my @userlist = @_;
my $goner;
my ($i, $confirm);
&queuelist(@userlist);
print "Which user do you wish to remove? ";
$goner = <>;
chomp $goner;
for $i ( 0 .. $#userlist ) {
if( $userlist[$i]->{username} eq $goner ) {
print "\nConfirm User Deletion\n";
print "---------------------\n";
&print_user_data(%{$userlist[$i]});
print "Remove this user from your queue? (Y/n) ";
$confirm = <>;
if( $confirm !~ /^n/i ) {
splice(@userlist,$i,1);
print "User $goner removed from queue.\n";
}
else {
print "Okay then, we'll leave this one alone.\n";
}
}
}
return @userlist;
}
sub queuedo {
my @userlist = @_;
my $i;
for $user (@userlist) {
if( $user->{username} ) {
print "\n>>> ADDING USER ", $user->{username}, ":\n";
&add_user(%{$user});
}
}
return;
}
# Self-explanatory ...
sub print_user_data {
my %user = @_;
print<<__EOUD;
username: $user{username}
fullname: $user{fullname}
password: $user{password}
shell: $user{shell}
__EOUD
}
# Get each piece of user data, calling appropriate get_ function until it
# returns 1
sub get_user_data {
my %user;
# while( $user{group} eq '' ) { $user{group} = &get_group; }
while( $user{username} eq '' ) { $user{username} = &get_username; }
while( $user{fullname} eq '' ) { $user{fullname} = &get_fullname; }
while( $user{password} eq '' ) { $user{password} = &get_password; }
while( $user{shell} eq '' ) { $user{shell} = &get_shell; }
return %user;
}
# Here's an example of a custom function used by EnterAct. Here we ask
# additionally for a 'Billing ID' to be stored in the log
# sub get_billing_id {
# print " Billing ID: ";
# my $billing_id = <>;
# chomp $billing_id;
# if( $billing_id =~ /^\d+$/ ) {
# return $billing_id;
# }
# print "I'm sorry, but I was hoping for a number.\n";
# return '';
# }
sub get_fullname {
print " Full Name: ";
my $fullname = <>;
chomp $fullname;
if( $fullname eq '' ) {
return "J. Doe";
}
if( $fullname =~ /^[\w\s\.\&\']*$/ ) {
return $fullname;
}
print "Names should be alphanumeric.\n";
return '';
}
# Here's another example of how you might want to specify going about setting
# group names. This is an EnterAct-specific example which prompts for a few
# different acceptable groups.
# sub get_group {
# while(1) {
# print "Choose from: dialin, mailbox, loyola, nologin\n";
# print " Which group? ";
# my $group = <>;
# if( $group =~ /^d/i ) {
# return 'dialin';
# }
# elsif( $group =~ /^m/i ) {
# return 'mailbox';
# }
# elsif( $group =~ /^l/i ) {
# return 'loyola';
# }
# elsif( $group =~ /^n/i ) {
# return 'nologin';
# }
# }
# }
# This one I like. It'll generate a random password if none is entered, using
# an algorithm that results in something a little easier to tell a customer
# over the phone than what pw generates but that should still be quite
# unpredictable.
#
# If a password is entered, it does a very basic sanity check on it to
# determine if it might be easily crack-able.
sub get_password {
my ($file, $confirm);
my @check_files = ('/etc/passwd', '/usr/share/dict/words');
my @ary = ( 0 .. 9, 'A' .. 'Z', 'a' .. 'z', 'z', '!', '$', '%');
print " Password: [random] ";
my $password = <>;
chomp $password;
if( $password ne '' ) {
foreach $file (@check_files) {
if( (system ("/usr/bin/grep", "-qw", $password, $file))/256 == 0 ) {
print "Ewww, no. That password is found in $file.\n";
print "This password is inexcusably lame, do you really want it? (Y/n) ";
$confirm = <>;
if( $confirm !~ /^n/i ) {
return $password;
}
else {
return '';
}
}
}
}
else {
my $pw_len = rand(5)+6;
for(1..$pw_len) {
$password .= $ary[rand(@ary)];
}
}
return $password;
}
# Another custom function used at EnterAct. This one actually got much more
# sophisticated with time. Seen here is an earlier version, that could still
# be interesting.
#
# sub get_referral {
# my @ary = (
# 'Current customer',
# 'Word of mouth',
# 'Additional account',
# 'Microcenter',
# 'CNET',
# 'Newsgroups',
# 'Loyola',
# 'Byte By Byte',
# 'Chicago Computer Guide',
# 'Digital Chicago',
# 'National-Louis',
# 'Lake Forest College',
# 'Web',
# 'Phone book',
# );
#
# foreach $n (0..@ary-1) {
# print " ", $n+1, "\) $ary[$n]\n";
# }
#
# print "Enter referral numer or other: ";
# my $referral = <>;
# chomp $referral;
# if( $referral =~ /^\d+$/ && $ary[$referral-1] ) {
# $referral = $ary[$referral-1];
# }
# return $referral;
# }
# This function will determine what shells are available in $pw_conf_path and
# offer these as choices
sub get_shell {
my($i, $shell, $shellstr, $default);
$shellstr = `/usr/bin/grep ^shells $pw_conf_path`;
$shellstr =~ s/.*?=\W*(.*)/$1/;
chomp $shellstr;
my @shells = split(/\W+/, $shellstr);
$default = $shells[0]; # Default shell is first choice listed.
$default or die "Not enough shells in $pw_conf_path!";
print " Shell: [$default] ";
$shell = <>;
chomp $shell;
$shell = $default unless $shell;
for $i ( 0 .. $#shells ) {
if( $shell eq $shells[$i] ) {
return $shell;
}
}
print "\"$shell\" is not a valid shell.\n";
print "Please select from among:\n @shells\n";
return '';
}
# If you are using a more modern version of FreeBSD and want to use usernames
# greater than eight characters, you need to change this function. Add/remove
# checks as desired.
sub get_username {
print " Username: ";
my $username = <>;
chomp $username;
$username =~ tr/[A-Z]/[a-z]/;
if( length($username) > 8 ) {
print "No, that username is too long.\n";
print "Usernames must be eight of fewer characters.\n";
return '';
}
if( length($username) < 3 ) {
print "No, that username is too short.\n";
print "Usernames must be three or more characters in length.\n";
return '';
}
if( $username !~ /^[a-z0-9]*$/ ) {
print "No, that username's not good.\n";
print "Usernames should consist solely of alphanumeric characters.\n";
return '';
}
if( $username !~ /^[a-z]/ ) {
print "I'm sorry, but usernames shouldn't start with numbers.\n";
return '';
}
if( (system "/usr/bin/id $username 2> /dev/null > /dev/null")/256 == 0 ) {
print "Ouch - that one's taken already.\n";
return '';
}
if( (system "/usr/bin/grep -q ^$username: /etc/aliases")/256 == 0 ) {
print "Ouch - that one's claimed as a mail alias.\n";
return '';
}
return $username;
}
# Calls pw to enter a user into the system. Did you properly configure
# /etc/pw.conf?
sub add_user {
my %user = @_;
my $logline;
my $username = $user{username};
my $fullname = $user{fullname};
my $password = $user{password};
my $shell = $user{shell};
# my $group = $user{group};
my $oldbuf = $|;
$| = 1;
# It's nice to finish what we start.
local $SIG{INT} = 'IGNORE';
print "Running pw ... ";
open( PW,
"| $pw_path useradd $username -c \"$fullname\" -m -s $shell -h 0" )
# "| $pw_path useradd $username -c \"$fullname\" -g $group -m -s $shell -h 0" )
or die "$pw_path failure: $!";
print PW $password, "\n";
close PW or warn "$pw_path exited on $?: $!";
print "DONE!\n";
print "Creating public directories ... ";
system("/bin/mkdir", "/home/$username/public_html");
system("/usr/sbin/chown", "-R", "$username.$username", "/home/$username/public_html");
# system("/usr/sbin/chown", "-R", "$username.$group", "/home/$username/public_html");
print "DONE!\n";
if( -s $welcome_message_path ) {
print "Queueing welcome message ... ";
open( WELCOME, $welcome_message_path )
or die "Couldn't open $welcome_message_path: $!";
open( MAIL, "| $sendmail_path -it " )
|| die "Couldn't open pipe to $sendmail: $0";
local $SIG{PIPE} = sub { die "Couldn't open pipe to $sendmail: $0" };
select MAIL;
print<<__MAIL;
To: $username\@$domain ($fullname)
Subject: Welcome to $organization!
__MAIL
while( <WELCOME> ) {
# This is better than exec()'ing arbitrary code as root, agreed?
s/\$name/$username/g;
s/\$fullname/$fullname/g;
s/\$password/$password/g;
print unless /^#/;
}
close MAIL || die "Error completing mail operation: $0";
select STDOUT;
print "DONE!\n";
}
print "Logging ... ";
$logline = localtime() . " $0 " . "$username (" . (getpwnam($username))[2] . "/" . (getpwnam($username))[3] . ") \"$fullname\"";
# $logline = localtime() . " $0 " . "$username/$group (" . (getpwnam($username))[2] . "/" . (getpwnam($username))[3] . ") \"$fullname\"";
&append_file($logfile, $logline);
print "DONE!\n";
$| = $oldbuf;
}
sub append_file {
my($filename,$line) = @_;
open(FH, ">>$filename") or warn
"Can't open $logfile: $!";
flock(FH,LOCK_EX);
seek(FH, 0, 2);
print FH $line, "\n";
close(FH);
flock(FH, LOCK_UN);
}
syntax highlighted by Code2HTML, v. 0.9.1