System administration in a multi-platform environment is a complex and demanding task. This paper will address some issues a system administrator will face. We will construct administrative tools using Perl programming language.
David N. Blank in his book, Perl for System Administration says, "System Administration is often a glue job; Perl is one of the best glue languages". I believe that Blank is implying that without system administration the computer networks would not hold together. To keep our systems together we need a language that will pull all the platforms together and keep them together.
Since Perl is an offspring of Unix Shells it easily adapts to Unix systems. Perl is equipped with system calls known from the C language. Perl is also is available on all platforms, which makes it useful for multi-platform networks. In addition to its flexibility Perl is also portable. Its portability is due to the extensive use of modules. With modules Perl's core language can be easily extended . Large collections of modules, for every administrative task have been implemented by a committed group of Perl developers.
Although Perl is a good tool for most system administration tasks it has its drawbacks. Perl isn't the best of all the object-oriented languages (not needed in system administration anyway). Perl is not always simple to understand and can be written in many different ways to do the same task. For the inexperienced programmer, system administration can be dangerous because of Perl's ability to manipulate system files and structures.
NO NEED TO REINVENT CODE. If Perl is to be effectively used as a tool for system administration it is necessary for the administrator to use modules. It is not efficient to reinvent code that someone else has invented. Perl modules enable an administrator to find the necessary tools and use them. Much of the benefit of Perl comes from the freely available collections of modules.
The best source for downloading Perl Modules is CPAN (Comprehensive Perl Archive Network). http://www.cpan.org.
Another way to find and install modules (in Windows) is with an application called PPM (Perl Package Manager). This tool uses the web to locate, download, and install modules with virtually no effort. This tool is free and can be obtained from the ActiveState web site. http://www.activestate.com/Products/ActivePerl//docs/faq/ActivePerl-faq2.html.
There are also resources that come with the Perl documentation which is included with a normal Perl installation. One such file is called perlmodinstall.pod and can be accessed by typing perldoc perlmodinstall at the command line.
Andrew Heuneman is the computer science lab administrator at Plattsburgh State University. He states that the default groups that are created for system users are done inefficiently and needs to be upgraded. Andrew states, "In Linux, the default is that every user has a default group of the same name as the user. On nexus6 that default was not used-most users got "users" as their default group but even that was not done consistently". As an example of how Perl can work for System Administration we will develop a possible solution.
In order to change to this new scheme a couple of files must be changed and a couple of things must be done to the files in the users home directory.
users:x:100: |
... mart1885:x:25000: marc1234:x:25001: suef2345:x:25002: fred3456:x:25003: Dorl4567:x:25004: ... etc. |
root2:x:0:0::/usr/local/custom2/home:/bin/tcsh csc221class:x:5461:6001::/usr/users/csc221class:/bin/tcsh plazaja:x:294:26:Jan Plaza:/usr/users/plazaja:/bin/tcsh mart1885:x:5130:100:William Martin:/usr/users/mart1885:/bin/tcsh mart6844:x:755:100:Ayme Martinez:/usr/users/mart6844:/bin/bash matu8063:x:831:100:Melissa Matusiak:/usr/users/matu8063:/bin/bash mccl1070:x:621:100:Jacob McClenahan:/usr/users/mccl1070:/bin/tcsh mcfa9172:x:617:100:Francis McFadyen:/usr/users/mcfa9172:/bin/bash |
root2:x:0:0::/usr/local/custom2/home:/bin/tcsh csc221class:x:1001:1001::/usr/users/csc221class:/bin/tcsh plazaja:x:594:594:Jan Plaza:/usr/users/plazaja:/bin/tcsh mart1885:x:15000:15000:William:/usr/users/mart1885:/bin/tcsh marc1234:x:15001:15001:Marcy:/usr/users/marc1234:/bin/tcsh suef2345:x:15002:15002:Suzy:/usr/users/suef2345:/bin/tcsh fred3456:x:15003:15003:Fred:/usr/users/fred3456:/bin/tcsh Dorl4567:x:15004:15004:Dory:/usr/users/Dorl4567:/bin/tcsh |
-rw------- 1 mart1885 users 186 Feb 12 19:24 file.txt -rwxr-xr-x 1 mart1885 users 293 Feb 12 19:24 file2.doc -rwxr-xr-x 1 mart1885 project1 888 Feb 12 19:24 file3.csv -rw------- 1 mart1885 users 1590 Sep 1 1999 file4.c drwxr-xr-x 10 mart1885 users 4096 Jan 22 1999 file5.cp drwx------ 4 mart1885 users 4096 Oct 25 12:04 file6.pl -rw-r--r-- 1 mart1885 users 496 Feb 12 19:24 file7.ada -rw------- 1 mart1885 users 12053 Sep 1 1999 file8.s drwx------ 3 mart1885 users 4096 Dec 15 14:55 hello.c |
-rw------- 1 mart1885 mart1885 186 Feb 12 19:24 file.txt -rwxr-xr-x 1 mart1885 mart1885 293 Feb 12 19:24 file2.doc -rwxr-xr-x 1 mart1885 project1 888 Feb 12 19:24 file3.csv -rw------- 1 mart1885 mart1885 1590 Sep 1 1999 file4.c drwxr-xr-x 10 mart1885 mart1885 4096 Jan 22 1999 file5.cp drwx------ 4 mart1885 mart1885 4096 Oct 25 12:04 file6.pl -rw-r--r-- 1 mart1885 mart1885 496 Feb 12 19:24 file7.ada -rw------- 1 mart1885 mart1885 12053 Sep 1 1999 file8.s drwx------ 3 mart1885 mart1885 4096 Dec 15 14:55 hello.c |
In order to address the project we first need to understand user account files. Our project will involve Linux user identity so NT/windows will not be covered. Two files will be involved in this project, /etc/passwd and etc/group since they contain the information that the system needs for user identification. We will leave out etc/shadow in this example because the file that we use in our endeavor will contain only the user id name, not the number.
The ASCII text file /etc/passwd is consistent across all the Unix based systems. By this I mean that the Linux format of the password file is the same as a SunOS format. Each line is colon separated and contains the following (IN ORDER FROM LEFT TO RIGHT):
root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin: daemon:x:2:2:daemon:/sbin: adm:x:3:4:adm:/var/adm: lp:x:4:7:lp:/var/spool/lpd: mail:x:8:12:mail:/var/spool/mail: news:x:9:13:news:/var/spool/news: operator:x:11:0:operator:/root: games:x:12:100:games:/usr/games: gopher:x:13:30:gopher:/usr/lib/gopher-data: ftp:x:14:50:FTP User:/var/ftp: nobody:x:99:99:Nobody:/: xfs:x:43:43:X Font Server:/etc/X11/fs:/bin/false rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/bin/false rpc:x:32:32:Portmapper RPC user:/:/bin/false mailnull:x:47:47::/var/spool/mqueue:/dev/null mart1885:x:500:500:William:/home/mart1885:/bin/bash marc1234:x:501:501:Marcy:/home/marc1234:/bin/bash suef2345:x:502:502:Suzy:/home/suef2345:/bin/bash fred3456:x:503:503:Fred:/home/fred3456:/bin/bash Dorl4567:x:504:504:Dory:/home/Dorl4567:/bin/bash |
If you would like to see your own line in the passwd file type $ grep yourloginname /etc/passwd
Group ids (GID), group names, and group members are kept tin the /etc/group file. When an account becomes a part of several groups it is added to numerous lines in the file. Each line is colon separated and contains the following (IN ORDER FROM LEFT TO RIGHT):
root:x:0:root bin:x:1:root,bin,daemon daemon:x:2:root,bin,daemon sys:x:3:root,bin,adm adm:x:4:root,adm,daemon lp:x:7:daemon,LP rpcuser:x:29: rpc:x:32: slocate:x:21: mart1885:x:500: marc1234:x:501: suef2345:x:502: fred3456:x:503: Dorl4567:x:504: |
Now we are ready to use our knowledge of user account files and design some applications to migrate our user accounts to the new structure. This section will explain the methods of implementation and the results.
The design of the proposed Perl Script was done in concideration to the requests of Dr. Plaza. He suggested taking a user one at a time and change the files necessary for that user only. In addition we will start the new uid's at 10000 because there are currently no uid's at that range.
The script will look into the /etc/passwd file and test each line for user type. Since the majority of the users are students we will be searching for user-name that begin with 2 to 4 characters and end with 4 digits. Once found, that users uid and group id will be changed . The name, user's old id, and home directory will be noted. The file will be opened read and copied into a temp file called passwd.tmp. Once we have located a student and changed their line to include the new uid and group id, we will write passwd.tmp back into the original passwd file. Just to play it safe we will have the script back up the original files before we do any file manipulation.
#BACKUP FILES
system ("cp /etc/passwd /etc/passwd.bkp");
print "\n Backed up passwd file to passwd.bkp\n";
system ("cp /etc/group /etc/group.bkp");
print " Backed up group file to passwd.bkp\n\n";
|
Here 'system("system call");' is how one would make system calls from Perl. The Perl interpreter forks a process which is the system call, then returns control back to the program after it has completed.
Once the passwd file has been changed then the new group name and id will be appended to the etc/group file. As explained in earlier sections the names and numbers will reflect the user-name and uid.
The hardest part takes place after the group file has been changed. We must recursively go into the users home directory and change the files groups. The files whose group ownership is not 100 or users will not be touched. It is also very necessary to not follow the symbolic links. We will use Perl and Linux commands that only look at files properties and not what they point to.
It is thought and suggested that the etc/shadow file must also be used but after talking to the labs administrator I have decided to leave it alone. On some systems the shadow file contains the uid number followed by the encrypted password and other stuff. In our instance the shadow file only contains the uid name and doesn't need to be changed so we will leave that file alone.
Of the three processes in our example project, changing the password file is the most interesting. Here we can see the beauty of Perl as it searches with ease then makes our changes without a fuss. Since we want our code to have three procedures, we construct a procedure named change_passwd. In Perl procedures are called subroutines and arguments are passed to it in an array @_. In this example we constructed a subroutine that takes two arguments $id and $min_uid. With $id we can specify the id number that we will replace the old students id with. The $min_uid will be a search argument (he minimum uid that we will search for).
The plan for this subroutine will be:
When we open files in Perl we use the > symbol to signify write and >> to signify append, otherwise they are opened for reading. Once open we search for the first student that has uid below 10000 (useful after we have already changed the file in a previous loop). We will search for the student pattern with the line:
m/^[a-z]{2,4}[0-9]{4}/
|
Here is where Perl is good. This line says search for instances that start with 2 to 4 characters (a to z) and that end with 4 digits (0-9). Another good Perl tool used in this example is the split call @line = split (/:/,$_);. What this does is split the line into the array @line with the delimiters : . This way we are able to pick out and change specific fields in each line of the passwd file. Here is our subroutine:
sub change_passwd {
my ($id, $min_uid) = @_;
my $file_name = "/etc/passwd"; #Password file
my $temp_file = "passwd.tmp"; #temp file
my $flag = 0; #did we find someone?
#OPEN FILES
open(FILE, $file_name) or die "Error opening $file_name: $!\n";
open(NEW_FILE, ">$temp_file") or
die "Error opening $temp_file: $!\n";
#COPY FILE AND CHANGE THE NEXT INSTANCE OF STUDENT WITH ID < $id
while (
|
Our example subroutine will exit and pass back to the calling routine three arguments $home_dir, $student, and $old_id . We will need the home directory of the student found for our third subroutine explained later. $student will hold the uid name and we will keep track of the students old uid with $old_id. These, we will also need in our other subroutines later.
Changing the group file will be the easiest part of the project code. Our game plan will be to open a file and append our new group. Notice that when we opened the group file we used >> to denote appending.
open(FILE, ">>$file_name") or die "Error opening $file_name: $!\n"; |
Here we are asking Perl to open the file for appending with the filehandle FILE, but if it cant, die gracefully by printing an error message. Here is our subroutine that changes the group file using the student uid number and name as arguments (obtained by the subroutine change_passwd):
sub change_group {
my ($id, $student) = @_;
my $file_name = "/etc/group"; #Group file
#OPEN FILE
open(FILE, ">>$file_name") or die "Error opening $file_name: $!\n";
#APPEND NEW GROUP
print FILE "$student:\:$id:\n";
print"Added $student to group file with group id: $id\n\n";
#CLOSE FILE
close(FILE);
}
|
The subroutine that changes the files' memberships in a students home directory will be the projects hardest task. Because Perl has the ability to do system calls and has file manipulation tools, our task can be done without too much hassel.
First off we needed a routine that would return a files owner and group. Let's make a subroutine called file_stat. Perl has a function called stat(). This function returns a 13-element list about a file and its statistics. To inquire about all files including symbolic links we cannot use stat, but instead we will use lstat(). lstat, when called on a symbolic link, returns info about that link and not the file that it points to. Now we have a routine that we need:
sub file_stat {
my($file) = @_;
my($dev, $ino, $mode, $nlink,
$uid, $gid, $rdev, $size,
$atime, $mtime, $ctime, $blksize,
$blocks) = lstat($file);
return ($uid, $gid);
}
|
Now that we have that out of the way we will do three things:
Our subroutine will take four arguments $dir, $id_to_change, $id, and $oid . $dir will be the student home directory which was obtained by the change_passwd subroutine. $id_to_change and $id are the ids to switch when we find the $id_to_change. The $oid argument is used to check to make sure the file is not owned by the student's old uid.
When we find a file that has a group membership that we want to change we will use the system call chgrp:
if ($grp_id == $id_to_change){
system ("chgrp", "-h", "$id", "$name");
}
|
This is where we must be careful that we do not follow symbolic links. According to the chgrp manpage, the -h argument is used to change the group of a symbolic link itself. It is important to note that this will fail if the system does not provide the lchown system call. If this call fails then the group does not get changed. The above code asks Perl, if this is a group id that we want to change then change this group but not the files that it may point to. We use \'$name\' for the file names that may have spaces in it like Red Hat Erratta.html. If we did not use quotes then the program would try to change the group of the files Red, Hat, and Erratta.html (would produce an error). Note that when we need to use a symbol that Perl might use in the wrong context we use a \, like the newline \n.
Sometimes we need to check and change the group of a directory. Perl comes to the rescue again by enabling us to test a name in the directory to see what it is. Here is how we will test for a directory and to make sure that it is not a link.
if (-d $name && !-l $name){
...
}
|
And so our subroutine is finished:
sub dir_scan{
my ($dir, $id_to_change, $id, $oid) = @_;
use Cwd;
Chadic($dir) or die "Cant change to $dir:$!\n";
open-air(DIR, ".") or die "Can't open $dir:$!\n";
my @names = readdir(DIR) or die "Can't read $dir:$!\n";
closed(DIR);
foreach my $name (@names){
next if ($name eq "..");
($usr_id, $grp_id) = &file_stat($name);
if ($usr_id == $oid){
chown($id, $grp_id, $name);
}
if ($name eq "."){
if ($grp_id == $id_to_change){
system ("chgrp -h $id \'$name\'");
}
next;
}
if (-d $name && !-l $name){
if ($grp_id == $id_to_change){
system ("chgrp -h $id \'$name\'");;
}
my $bookmark = &cwd; #we must mark our spot before recursing
&dir_scan($name, $id_to_change, $id);
Chadic($bookmark) or die "Cant change to $bookmark:$!\n";
next;
}
if ($grp_id == $id_to_change){
system ("chgrp -h $id \'$name\'");
}
}
}
|
Finally we put all our subroutines into a loop:
while(1){
#CHANGE PASSWORD FILE
($home_directory, $stdnt, $old_id) =
&change_passwd($count, $min_id);
if ($old_id == 0){ #Will be zero if no more students!
print" There are no more students!\n";
last;
};
#CHANGE GROUP FILE
&change_group($count, $stdnt);
#CHANGE USERS FILES IN HOME DIRECTORY
&dir_scan($home_directory, 100, $count, $old_id);
print "Would you like to continue? ";
$answer =
|
Now we have a System Administration tool done using the Perl language and thus our project example is finished. This code was tested on a mock Linux system created to mirror our own labs scheme.
I have shown that Perl is a good choice for System Administration and have demonstrated it by changing the group scheme to a Linux like scheme. We have done this by writing some example code and hopefully you have seen that Perl has roots in Unix shells and easily makes system calls. Also you have seen some of Perl's many tools for doing administrative tasks.
I am a mountain biker at heart and I wouldn't be caught dead without my Allen (wrench) set. With this tool a person can completely take apart a modern mountain bike and put it back together. Perl is to the System Administrator that this tool is to me.
As with any project the ultimate goal is to learn something new and expand our knowledge. I hope that you have benefited as much as I have from this project. In the future Perl could be used in the lab for tools that enable students to create their own groups. Perl is also effective on the windows platform and could be used for user accounts on the Windows 2000 side of our lab.
Here is an example of the latest code in its entirety (newscheme.pl):
#/usr/bin/perl
my $count = 10019; #START COUNT HERE
my $min_id = 10000; #CHANGE ALL UIDS LESS THAN THIS
my $old_id = 1; #INITIALIZE FLAG
#####change_passwd###################################################
# This sub-program opens the etc/passwd file and copies it to
# passwd.tmp in this directory untill it finds the first
# student (2 to 4 letters followed by 4 digits). When
# encountered the user id is changed to the initial id number.
# This new id is given by the variable $id in which is
# incremented. The passwd.tmp is then used to overwrite the
# /etc/passwd file.
#
# INPUT: the start id number and the number that all
# ids < will be changed(parameters)
# OUTPUT: updated passwd file as well as passwd.bkp
# DEPENDENCIES: none
# DATE: March 26, 2001
# AUTHOR: William Martin
#####################################################################
sub change_passwd {
my ($id, $min_uid) = @_;
my $file_name = "/etc/passwd"; #Password file
my $temp_file = "passwd.tmp"; #temp file
my $flag = 0; #did we find someone?
#OPEN FILES
open(FILE, $file_name) or die "Error opening $file_name: $!\n";
open(NEW_FILE, ">$temp_file") or
die "Error opening $temp_file: $!\n";
#COPY FILE AND CHANGE THE NEXT INSTANCE OF STUDENT WITH ID < $id
while (
|