Monthly Columns
 

Enteruser: A Replacement for Adduser

Copyright © 1999 Dannyman

ABSTRACT

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.

ACKNOWLEDGMENTS

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.

INTRODUCTION: WHY I DON'T LIKE ADDUSER

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.

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.

USING ENTERUSER

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
Password:
# 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
%cd
%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.

USING QUEUEUSER

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.

HACKING ENTERUSER

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.

PERFORMANCE MODIFICATIONS

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.

Dannyman, dannyman@dannyland.org