Re: Noch ein gaaanz großes Problem

From: Marc Santhoff <M.Santhoff(at)>
Date: Mon, 11 Jun 2001 21:46:43 +0200

Hi Dejan!

Dejan Grujin wrote:
> Problem besteht noch...
> Wäre nett, wenn Du das tun könntest
> Gruß Dejan

Klar, hab's sogar gefunden.

Das Ding heißt 'Enteruser' und scheint tatsächlich für
interaktiven Betrieb geschrieben zu sein. Vielleicht
reicht dein Perl ja für einen Hack, um die Datenbank als
Eingabe zu benutzen.

Das ganze müßte auf liegen.

Was ich noch da habe , hängt an dieser Mail.




This is a pretty output of this script. The useful, runnable perl script lives here.

# 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 
# for EnterAct, LLC, 1998. (
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;

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/ ) {
else {
###  END PROGRAM  ###

sub enteruser {
	my %newuser;

	my $y_or_n;
	%newuser = &get_user_data;
	print "\nVerify User Information\n";
	print "-----------------------\n";
	print "\nIs this okay? (Y/n) ";
	$y_or_n = <>;
	if( $y_or_n !~ /^n/i ) {

sub queueuser {
	my @userqueue;
	my $choice;

while(1) {
	my $yn;


   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
		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 ) {
		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 ) {
			else {
		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 "\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);

	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 "Remove this user from your queue? (Y/n) ";
			$confirm = <>;
			if( $confirm !~ /^n/i ) {
				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";

# Self-explanatory ...
sub print_user_data {
	my %user = @_;
   username: $user{username}
   fullname: $user{fullname}
   password: $user{password}
      shell: $user{shell}

# 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;
To: $username\@$domain ($fullname)
Subject: Welcome to $organization!

		while(  ) {
			# This is better than exec()'ing arbitrary code as root, agreed?
			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: $!";
	seek(FH, 0, 2);
	print FH $line, "\n";
	flock(FH, LOCK_UN);

Daemon News 199908 : Enteruser: A Replacement for Adduser

Monthly Columns

Enteruser: A Replacement for Adduser

Copyright © 1999 Dannyman


This article features a user-friendly script written for the purposes of adding users to a FreeBSD system. The article first explains the rationale for not using a pre-existing script - adduser. It then explains the rationale behind the design of the replacement script - enteruser, and provides an example of its use. The script is written in Perl and the article details an example of how the script could be modified to extend functionality. For this section, the reader should be comfortable hacking Perl. At the conclusion, this article touches on some advanced modifications that could be made to lower levels of the user addition section to make things faster on heavily-populated systems.


Thanks are owed to EnterAct Corp, for making the enteruser source code publicly available. Thanks also to Patrick McCormick of Tellme Networks Inc, for lending me a proof-read, and of course, to Daemon News for featuring this and my previous article.


I spent the summer and fall of 1998 working for a Chicago-based Internet Service Provider. On the systems team, I had the opportunity to explore first-hand how well FreeBSD could scale to handle thousands of users.

One of the first problems I tackled was the way in which new users were added into the system. When the ISP first started, it was sufficient to run adduser while the customer was on the phone and voila! - they were online. However, there were two problems with running adduser.

The first problem is a race condition where if adduser is run multiple times, it will eventually start duplicating user IDs, as a user ID may be chosen in one session while the system is being updated with that already-chosen ID. Adduser relies on pwd_mkdb, which can have some rather freaky race conditions of its own when invoked multiple times, which is another thing multiple sessions of adduser encourage.

The second problem is that adduser was not designed with an eye toward adding extra functionality as time passed. As our organization grew, so did the number of things we wanted adduser to do. Logging was extended to help track referrals, for example, and various technical procedures for account setups were introduced. As I hustled around trying to repair the first problem, the second problem became an increasing concern.

Something had to be done. And I figured that something was enteruser.


I had a significant advantage over adduser's author, Wolfram Schneider, in that by the time I needed to write enteruser, most of the hard stuff with modifying and rebuilding the user database had already been implemented by the pw command. Pw is designed to add users in one command-line, but also with some functionality in mind when it comes to being incorporated into a script. I talked about pw in May's Daemon News column.

With the "hard stuff" out of the way, I was free to concentrate on the interface. After all, enteruser is mostly a glorified interface for pw. So why write enteruser at all when pw has the same functionality? Well, pw doesn't have all the functionality you might need - for example, it doesn't create a public_html directory, or keep a log of its activity.

Also, pw is not something you want other employees or colleagues running, as it takes some time to learn and has enough power to do some really scary things to the system. The fewer people with "really scary" access the better. By simplifying the ways in which one might call pw, enteruser makes things easier, safer, and more secure.

Along with the interface, I wanted enteruser to be extensible, so the next time someone asked for a feature it could be incorporated without too much fuss. I wrote it in Perl and broke things out into functions wherever possible. I'll delve into this below.

Another thing that other employees wanted was a way to enter information for several users at once and then kick back and let the script do its work. Part of the reason for this is because once you have a few thousand users, the process of rebuilding the user database becomes noticeably slower. One of the first extensions I built into enteruser was an additional interface called queueuser.


The version of enteruser featured in this article will work only if you've configured pw. If you want to try enteruser out, either review the section on pw in May's article or steal a friend's pw.conf. I'd share mine but it's unavailable at the moment.

Try installing enteruser in your path somewhere, and make it executable. You'll need Perl5 installed in /usr/bin, which is no problem if your system is fairly current. Otherwise, change the shebang (top line) to point at your Perl5, probably /usr/local/bin/perl.

And fire it up!

0-20:32 dannyman@stumpy ~> enteruser
You must be root to run /home/dannyman/bin/enteruser!
255-20:32 dannyman@stumpy ~> su
# enteruser
                     Username: dannyman
Ouch - that one's taken already.
                     Username: danny
                    Full Name: Danny D Man
            Password: [random] dannyman
Ewww, no.  That password is found in /etc/passwd.
This password is inexcusably lame, do you really want it? (Y/n) n
            Password: [random]
                 Shell: [sh] tcsh

Verify User Information
   username: danny
   fullname: Danny D Man
   password: 3FK$VALfmS
      shell: tcsh
Is this okay? (Y/n)
Running pw ... DONE!
Creating public directories ... DONE!
Logging ... DONE!
# su danny
%ls -a
.               .login          .mailrc         .shrc
..              .login_conf     .profile        .xsession
.cshrc          .mail_aliases   .rhosts         public_html
%tail -1 /var/log/enteruser.log
Tue May  4 20:33:28 1999 /home/dannyman/bin/enteruser danny (1013/1013)
"Danny D Man"
Rocket science it's not. You could also put a welcome message in /etc/adduser.message and it will send that along - just like adduser, only a little more secure.


Queueuser is enteruser. If you run enteruser as a link to queueuser, enteruser will run as queueuser - same script, two different beasts.

Using queueuser is almost exactly like using enteruser, because they both rely on the same functions, only queueuser has more:

# queueuser
   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
What would you like to do? q

Self-explanatory enough? The first option will prompt for information using the same function used in enteruser. List and delete are useful for finding and removing mistakes. Once you have entered a list of users and are satisfied with them, entering "p" will effectively cause enteruser to be invoked once per user.


I find the first step to hacking something is to get a printout of it and whatever relevant dependencies it needs, and take that and possibly a good reference book to the bathroom, or some other place where you can dwell with your own thoughts without being disturbed. Of course, if the whole world was like me, we might need more bathrooms. The point is, in an ideal world, you'll take a nice leisurely stroll through a printout of enteruser, and hopefully come to grok it with ease.

The &enteruser subroutine itself is about ten lines, with most of the work divided between &get_user_data, and &add_user. The &get_user_data subroutine calls four more subroutines, one for each item of data you want to get. If one wanted to extend enteruser to set, say, group information, one would merely need to change &get_user_data and &add_user, and write a &get_group subroutine. Of course, they would also want to modify the &print_user_data and &queuelist operations.

The &queueuser subroutine is a little more complex, calling &get_user_data through &queueadd and &add_user through &queuedo, and featuring &queuelist and &queuedel, for listing and deleting users respectively. You'll note that each menu option in queueuser, with the exception of "quit" is broken out into a function. Adding another option should be trivial, except for the fact that handling recursive data structures in Perl can be a tricky thing to deal with. Here we are dealing with an array of hashes. If things get tricky here, check out the perldsc man page for illumination.

For some fairly instant gratification, go in there and incorporate a feature for setting a user's primary group. This is pretty trivial because the functionality used to be there and I left it in there in commented form. A checklist:

  1. Uncomment line in &get_user_data that calls &get_group.
  2. Uncomment the &get_group function and tailor it to your needs. If you're following along just for the educational experience, then pretend that you run an ISP and go create groups dialin, mailbox, loyola and nologin groups.
  3. Go through &add_user, and swap out the three lines where you want to incorporate group information. This is extremely easy, because each of these lines has a counterpart with group information commented out below. You want to hit the open call to $pw_path, the chown system call, and the format of $logline right before the &append_file call. Don't forget to set the $group variable up top!
  4. If you are a tidy sort, hit &print_user_data and &queuelist appropriately for the desired effect.

See how easy it is? This checklist should be handy for doing basic feature additions to enteruser even when the subroutine isn't already written.


If your system is growing to support several thousand users, you'll start to find that pw runs really slow. This can be modified by tweaking the calls that pw makes to pwd_mkdb. Unfortunately, this involves hacking pw's source code.

The story is like this: the more users you have, the slower pwd_mkdb runs. There are two things that will make pwd_mkdb run faster.

  1. Call pwd_mkdb -u. This tells pwd_mkdb to rebuild the database for just one user.
  2. Increase pwd_mkdb's memory cache. This can be done by calling pwd_mkdb -s on 3.x systems. Earlier versions require pwd_mkdb's source code to be modified.

It took me several hours of stumbling around the source code trying to figure this stuff out. Unfortunately, the machine I kept my patches on won't have net access until July. I hope to follow this last bit up in next month's issue with the patches in question. If you are eager to tackle this right away, I can say to look into the calls to pwdb() in /usr/src/usr.sbin/pw/pwupd.c. Of course, you'll want to understand how pwd_mkdb works, and this is explained in my previous column.



To Unsubscribe: send mail to majordomo(at)
with "unsubscribe de-bsd-questions" in the body of the message
Received on Mon 11 Jun 2001 - 21:44:19 CEST

search this site