\documentclass{ictlab} \RCS $Revision: 1.3 $ \usepackage{alltt,xr,url} \usepackage[breaklinks,pdfpagemode=None,pdfauthor={Nick Urbanik}]{hyperref} \externaldocument[gr-]{../../../ossi/lab/grub/grub} \newcommand*{\labTitle}{Assignment 3: Creating bulk \POSIX accounts in an LDAP Directory} \begin{Solutions}% \gdef\solution{\paragraph{Solution:}}% \end{Solutions} \providecommand*{\MD}{\acro{MD}\xspace} \begin{document} \paragraph{Submission:} by 8pm, Sunday, 23 May 2004 \paragraph{Where:} Online at \url{http://ictlab.tyict.vtc.edu.hk/perl2/submit.cgi}. A paper submission is not required (save the trees!) \paragraph{Important:} All programs you write must start with these lines: \begin{verbatim} #! /usr/bin/perl use warnings; use strict; \end{verbatim} and should compile without errors. \paragraph{Cheating:} Your work \emph{must} be original. Copying will be \emph{severely} dealt with. \section{Background} \label{sec:background} This assignment requires the use of the \texttt{Net::LDAP} Perl module. There is plenty of documentation that comes with the software. I have made a unique ``student registration system file'' for each student in the class. Please make sure that you use the correct one as your input data, or you will receive \emph{zero marks} for the output from your program. You will create several hundred \LDAP \POSIX accounts from this student registration system file. Feel free to reuse the code you wrote for your first Perl account creation assignment, since the format of the data has not changed significantly. You may wish to use the standard \texttt{Getopt::Long} Perl module as a way to pass options to your software. See \texttt{perldoc Getopt::Long}. %% \subsection{Reading command line parameters} %% \label{sec:cammand-line-parameters} %% There is a builtin array \texttt{@ARGV} in Perl that holds the %% \emph{command line arguments}, or \emph{command line parameters}. %% Try this program to see what happens: %% \begin{verbatim} %% #! /usr/bin/perl -w %% use strict; %% for ( my $i = 0; $i < @ARGV; ++$i ) %% { %% print "argument $i is $ARGV[ $i ]\n"; %% } %% \end{verbatim} %% If the program was called \texttt{argv.pl}, you could run it like this: %% \begin{verbatim} %% argv.pl This is the command line. "Some more on the command line." %% \end{verbatim} \section*{Assignment Requirements} \label{sec:requirements} \begin{enumerate} \item Write a program that can read the data provided at \texttt{http://\allowbreak{}ictlab.\allowbreak{}tyict.\allowbreak{}vtc.\allowbreak{}edu.\allowbreak{}hk/\allowbreak{}snm/\allowbreak{}assignments/\allowbreak{}assignment-perl-ldap/\allowbreak{}data/\allowbreak{}artificial-\allowbreak{}student-\allowbreak{}data-\allowbreak{}\meta{Your student ID}\meta{Your .txt}}, where \meta{Your student ID} is your nine-digit student number. Your program will generate \LDIF to create a \POSIX \LDAP account for each user, and create a single private user group for each user. Make each user a primary member of their own private user group. \item As an option, your program should be able to generate accounts directly on the directory server. In this case, your software should check whether the account or group entry exists before creating it, and should inform the user about all error conditions. \paragraph{Note:} I no longer recommend that you use the built in Perl functions \texttt{getpwent} and \texttt{getgrent} to determine the next available unused \texttt{uidNumber} and \texttt{gidNumber}. I suggest that instead you either: \begin{itemize} \item simply start the \texttt{uidNumber} and \texttt{gidNumber} from 2000 if your directory has no accounts, since these numbers are only relevant in your own directory. \item[OR:] \item use the method that I outlined in an email I sent you earlier, where you search for the highest \texttt{uidNumber} and \texttt{gidNumber} values in your directory, and start with a number 1 greater than that, or with 2000 if there are no accounts in your directory. \end{itemize} \item The root of your directory shall be \texttt{ou=\meta{Your Student ID},o=ICT}. The user accounts shall be under \texttt{ou=People,ou=\meta{Your Student ID},o=ICT}. The groups shall be under \texttt{ou=Group,ou=\meta{Your Student ID},o=ICT}. \item Each user account entry shall be a member of the following object classes: \texttt{posixAccount}, \texttt{shadowAccount} and any suitable \texttt{structural} class such as \texttt{inetOrgPerson}. \item Each group entry should be a member of the \texttt{posixGroup} object class, and should contain an attribute \texttt{cn} which is equal to the user \ID for whom this is a private group. It should also contain an attribute \texttt{gidNumber} with a unique integer number greater than 1999 that has the same value as the \texttt{gidNumber} attribute that is in the corresponding user entry. \item Each user account should have an attribute \texttt{loginShell} with the default value \texttt{/bin/bash}, and an attribute \texttt{homeDirectory} with the value \texttt{/home/\meta{user name}}. Each user entry should also have a \texttt{gidNumber} attribute (as mentioned above), and a \texttt{uidNumber} attribute. Both the \texttt{uidNumber} and \texttt{gidNumber} attributes should have unique values over 1999. \item The program should also provide an option to generate \LDIF to delete the accounts read from the file(s) in the input. \item The program does not need to create a home directory for each user. \item The username should be the student number of the student and shall be stored in the \texttt{uid} attribute. \begin{explanation} For example, for a student with the name ``YEUNG, Hoi Man,'' and the student number 915367894, the user name that the person logs in with is \texttt{915367894}. \end{explanation} \item The password shall be their Hong Kong \ID, with letters in \emph{lower} case, but \texttt{not} including the parentheses or the check digit. So for a student with the Kong Kong \ID A456789(A), their password should be \texttt{a456789}. This means that there will be seven characters in each password. You are to create a \texttt{userPassword} attribute in each account, putting into it the text which is the \MD{}5 hash of the password. You can create this using either the \texttt{unix\_md5\_crypt()} method of the \texttt{Crypt::PasswdMD5} module you can install from \CPAN, or use the \texttt{hash\_md5\_password()} function you used in your previous assignment. The \texttt{userPassword} attribute should begin with the string \texttt{\{crypt\}}. So for example, if a student number is A123456(3), then the output of: \begin{verbatim} use Crypt::PasswdMD5; our $clear_passwd = shift || 'a123456'; our $hashed_password = unix_md5_crypt $clear_passwd; print "{crypt}$hashed_password\n"; \end{verbatim} may look like this: \begin{alltt} \textbf{$ ./make-password.pl} \{crypt\}$1$xM8WAHwg$qwA3/nAhJRSSQ75qM6IdT0 \end{alltt} and be suitable as a value for the \texttt{userPassword} attribute. \item You should include the full name of each student in their \texttt{cn} attribute, their family name will be stored in the \texttt{sn} attribute, their given name will be stored in the \texttt{givenName} attribute. \item More marks will be awarded for solutions that are more general, more flexible and of course, better structured. \item Also, with the soft copy of your program, provide a soft copy of the following: \begin{itemize} \item an \LDIF file output from your program that creates all the accounts and creates the private user groups for all the students in the artificial student data that was allocated to you. \item an \LDIF file output from your program that deletes all the accounts and private user groups listed in the artificial student data file that was allocated to you. \end{itemize} \item Your program should display an informative error message for each account that fails to be created or deleted. \item You should be prepared to demonstrate your program in the laboratory. \end{enumerate} If you have any questions about the assignment, please ask them by email. I will send the reply to all students if that seems helpful, but I will conceal the identity of the person who asked the question. I welcome any questions. \begin{solution} This is not really a solution, but is part of one possible solution. This handles the requirements for accounts on Linux. \begin{verbatim} #! /usr/bin/perl -w use strict; use Getopt::Long; our $add = 0; our $del = 0; our $remove_dir = 0; sub usage { die "usage: $0 --add|(--del [--rem])\n"; } GetOptions( add => \$add, del => \$del, rem => \$remove_dir ) or usage(); usage unless $add or $del; usage if $add and $del; usage if $remove_dir and $add; sub hash_md5_password($) { my $clear_text_password = shift; my $salt = join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64, rand 64, rand 64, rand 64, rand 64, rand 64, rand 64]; $salt = '$1$'.$salt.'$'; my $hashed_password = crypt( $clear_text_password, $salt ); return $hashed_password; } my $course; my $year; while ( <> ) { chomp; if ( m!^(\d+)/(\d)\s! ) { $course = $1; $year = $2; next; } if ( my ( $name, $gender, $student_id, $hk_id ) #= m!\s\s+([A-Z]+(?: [A-Z][a-z]*)+)\s\s+([MF])\s+(\d{9})\s\s+([a-zA-Z]\d{6}\([\dA-Z]\))! ) = m{ \s+ # at leaset 1 space ( # this matches $name [A-Z]+,? # family name is upper case (?:\s[A-Z][a-z]*)+ # one or more given names ) \s\s+ # at leaset 2 spaces ([MF]) # gender \s+ # at least one space (\d{9}) # student id is 9 digits \s\s+ # at leaset 2 spaces ([a-zA-Z]\d{6}\([\dA-Z]\)) # HK ID }x ) { print "sex=$gender, student ID = $student_id, ", "hkID = $hk_id, course = $course, name=$name, ", defined $year ? "year = $year\n" : "\n"; my ( $full_name, $userid, $clear_password ) = ( $name, substr( $name, 0, 1 ) . $student_id, $hk_id ); our $hashed_password = hash_md5_password( $clear_password ) if $add; my @cmd = ( '/usr/sbin/useradd', '-c', "\"$full_name\"", '-p', "$hashed_password", "$userid" ) if $add; @cmd = ( '/usr/sbin/userdel', "-r", "$userid", ) if $del; splice @cmd, 1, 1 if $del and not $remove_dir; # The "and " is just to pause the display when there is # a problem: system( @cmd ) == 0 or ( warn "Cannot execute @cmd: $!" and ); next; } # Just to check there are no lines that we have missed: warn "POSSIBLE UNMATCHED STUDENT: $_\n" if m!^\s*\d+\s+!; } \end{verbatim} Here is a simple test program to generate one account, written before developing the program above: \begin{verbatim} #! /usr/bin/perl -w # Example program to generate MD5 hashed passwords suitable for use in # /etc/shadow on a Linux system. # You could pass the output of this function to useradd -p xxxxx, # where xxxxx is the output of hash_md5_password(). # Based on /usr/share/doc/samba-2.2.1a/examples/LDAP/ldapsync.pl, # distributed with samba. # A portable alternative is the module Crypt::PasswdMD5, available # through the cpan program. use strict; sub hash_md5_password($) { my $clear_text_password = shift; my $salt = join '', ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64, rand 64, rand 64, rand 64, rand 64, rand 64, rand 64, rand 64]; $salt = '$1$'.$salt.'$'; my $hashed_password = crypt( $clear_text_password, $salt ); return $hashed_password; } # This code is a stub just to test the function: our $clear_password = "test"; our $hashed_password = hash_md5_password( $clear_password ); print "MD5 Hash of '$clear_password' is '$hashed_password'\n"; my ( $full_name, $userid ) = ( 'Test One', 'tst1' ); my @cmd = ( '/usr/sbin/useradd', '-c', "\"$full_name\"", '-p', "$hashed_password", "$userid" ); system( @cmd ) == 0 or die "Cannot execute @cmd: $!"; \end{verbatim}%$ \end{solution} \end{document} $ ldapsearch -x '(&(year=3)(course=41300)(registrationDate=*03))' uid -LLL | grep '^uid: ' | cut -d' ' -f2 | wc -l 80 [nicku@nickpc] 4y 6m 9d, 10:1:54 ~/teaching/ict/snm/lab/namelist 13w4d 10:58:6 $ ldapsearch -x '(&(year=3)(course=41300)(registrationDate=*03))' uid -LLL | grep '^uid: ' | cut -d' ' -f2 | while read uid;do ./generate-fake-enrolment-data --format-file=format-for-assignment-2003-2004.txt /home/nicku/admin/ict/student-data/ft.txt; mv /home/nicku/admin/ict/student-data/ft.txt.fictionalised /home/nicku/admin/ict/student-data/artificial-student-data-${uid}.txt;done $this_max_class=150 $this_max_class=157 $this_max_class=141 $this_max_class=150 $this_max_class=141 $this_max_class=141 $this_max_class=144 $this_max_class=146 $this_max_class=146 $this_max_class=142 $this_max_class=142 $this_max_class=152 $this_max_class=147 $this_max_class=152 $this_max_class=146 $this_max_class=144 $this_max_class=156 $this_max_class=147 $this_max_class=145 $this_max_class=158 $this_max_class=158 $this_max_class=152 $this_max_class=155 $this_max_class=144 $this_max_class=141 $this_max_class=153 $this_max_class=145 $this_max_class=150 $this_max_class=154 $this_max_class=152 $this_max_class=150 Use of uninitialized value in string ne at ./generate-fake-enrolment-data line 607, line 3867. $this_max_class=156 $this_max_class=145 $this_max_class=143 $this_max_class=141 $this_max_class=145 $this_max_class=148 $this_max_class=145 $this_max_class=156 $this_max_class=142 $this_max_class=148 $this_max_class=154 $this_max_class=146 $this_max_class=158 $this_max_class=155 $this_max_class=154 $this_max_class=156 $this_max_class=151 [nicku@nickpc] 4y 6m 9d, 10:9:39 ~/teaching/ict/snm/lab/namelist 13w4d 10:50:21 $ $ egrep -c '[A-Z][0-9]{6}\([0-9A]\)' artificial-student-data-* artificial-student-data-000713210.txt:923 artificial-student-data-010042338.txt:927 artificial-student-data-010074127.txt:929 artificial-student-data-010077977.txt:921 artificial-student-data-010145653.txt:930 artificial-student-data-010145917.txt:940 artificial-student-data-010184168.txt:922 [nicku@nickpc] 4y 6m 9d, 10:15:37 ~/admin/ict/student-data ~/teaching/ict/snm/lab/namelist 13w4d 10:44:22 $