Annotation of loncom/build/make_domain_coordinator.pl, revision 1.16
1.1 harris41 1: #!/usr/bin/perl
2:
3: =pod
4:
5: =head1 NAME
6:
7: make_domain_coordinator.pl - Make a domain coordinator on a LON-CAPA system
8:
1.2 harris41 9: =cut
10:
11: # The LearningOnline Network
12: # make_domain_coordinator.pl - Make a domain coordinator on a system
13: #
1.16 ! raeburn 14: # $Id: make_domain_coordinator.pl,v 1.15 2008/03/03 15:25:02 www Exp $
1.2 harris41 15: #
16: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
17: #
18: # LON-CAPA is free software; you can redistribute it and/or modify
19: # it under the terms of the GNU General Public License as published by
20: # the Free Software Foundation; either version 2 of the License, or
21: # (at your option) any later version.
22: #
23: # LON-CAPA is distributed in the hope that it will be useful,
24: # but WITHOUT ANY WARRANTY; without even the implied warranty of
25: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26: # GNU General Public License for more details.
27: #
28: # You should have received a copy of the GNU General Public License
29: # along with LON-CAPA; if not, write to the Free Software
30: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
31: #
32: # /home/httpd/html/adm/gpl.txt
33: #
34: # http://www.lon-capa.org/
35: #
36: ###
37:
38: =pod
39:
1.1 harris41 40: =head1 DESCRIPTION
41:
42: Automates the steps for domain coordinator creation. This
43: program also describes a manual procedure (see below).
44:
45: These are the steps that are executed on the linux operating system:
46:
47: =over 4
48:
49: =item *
50:
51: Tests to see if user already exists for linux system or for
1.7 harris41 52: LON-CAPA, if so aborts. A message is output that recommends following
53: a manual procedure enabling this user if so desired.
1.1 harris41 54:
55: =item *
56:
57: Creates a linux system user
58:
59: =item *
60:
61: Sets password
62:
63: =item *
64:
65: Creates a LON-CAPA lonUsers directory for user
66:
67: =item *
68:
69: Sets LON-CAPA password mechanism to be "unix"
70:
71: =item *
72:
73: Set roles.hist and roles.db
74:
75: =back
76:
77: =cut
78:
79: # NOTE: I am interspersing the manual procedure with the automation.
80: # To see the manual procedure, do perldoc ./make_domain_coordinator.pl
81:
82: # This is a standalone script. It *could* alternatively use the
83: # lcuseradd script, however lcuseradd relies on certain system
1.7 harris41 84: # dependencies. In order to have a focused performance, I am trying
85: # to avoid system dependencies until the LON-CAPA code base becomes
86: # more robust and well-boundaried. make_domain_coordinator.pl should be able
87: # to run freely as possible, irrespective of the status of a LON-CAPA
1.1 harris41 88: # installation.
89:
90: # ---------------------------------------------------- Configure general values
91:
1.10 albertel 92: use lib '/home/httpd/lib/perl/';
93: use LONCAPA;
1.13 raeburn 94: use LONCAPA::lonmetadata;
95: use DBI;
1.1 harris41 96:
97: =pod
98:
99: =head1 OPTIONS
100:
101: There are no flags to this script.
102:
103: usage: make_domain_coordinator.pl [USERNAME] [DOMAIN]
104:
1.3 harris41 105: The password is accepted through standard input
106: and should only consist of printable ASCII
107: characters and be a string of length greater than 5 characters.
1.1 harris41 108:
109: The first argument
110: specifies the user name of the domain coordinator and
111: should consist of only alphanumeric characters.
1.8 harris41 112: It is recommended that the USERNAME should be institution-specific
113: as opposed to something like "Sammy" or "Jo".
114: For example, "dcmsu" or "dcumich" would be good domain coordinator
115: USERNAMEs for places like Mich State Univ, etc.
1.1 harris41 116:
1.3 harris41 117: The second argument specifies the domain of the computer
1.12 albertel 118: coordinator.
1.1 harris41 119:
120: =cut
121:
122: # ----------------------------------------------- So, are we invoked correctly?
123: # Two arguments or abort
124: if (@ARGV!=2) {
1.8 harris41 125: die('usage: make_domain_coordinator.pl [USERNAME] [DOMAIN] '."\n".
126: '(and password through standard input)'."\n".
127: 'It is recommended that the USERNAME should be institution-specific '.
128: "\n".'as opposed to something like "Sammy" or "Jo".'."\n".
129: 'For example, "dcmsu" or "dcumich" would be good domain coordinator'.
130: "\n".'USERNAMEs for places like Mich State Univ, etc.'."\n");
1.1 harris41 131: }
132: my ($username,$domain)=(@ARGV); shift @ARGV; shift @ARGV;
1.12 albertel 133: if ($username=~/$LONCAPA::not_username_re/) {
1.7 harris41 134: die('**** ERROR **** '.
1.12 albertel 135: 'Username '.$username.' must consist only of - . and alphanumeric characters'.
1.7 harris41 136: "\n");
1.1 harris41 137: }
1.12 albertel 138: if ($domain=~/$LONCAPA::not_domain_re/) {
1.7 harris41 139: die('**** ERROR **** '.
1.12 albertel 140: 'Domain '.$domain.' must consist only of - . and alphanumeric charaters and '.
1.7 harris41 141: "\n");
1.1 harris41 142: }
143:
1.7 harris41 144: # Output a warning message.
145: print('**** NOTE **** '.
146: 'Generating a domain coordinator is "serious business".'."\n".
147: 'Choosing a difficult-to-guess (and keeping it a secret) password '."\n".
148: 'is highly recommended.'."\n");
149:
150: print("Password: "); $|=1;
1.1 harris41 151: my $passwd=<>; # read in password from standard input
152: chomp($passwd);
153:
154: if (length($passwd)<6 or length($passwd)>30) {
1.7 harris41 155: die('**** ERROR **** '.'Password is an unreasonable length.'."\n".
156: 'It should be at least 6 characters in length.'."\n");
1.1 harris41 157: }
158: my $pbad=0;
159: foreach (split(//,$passwd)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
160: if ($pbad) {
1.7 harris41 161: die('**** ERROR **** '.
162: 'Password must consist of standard ASCII characters'."\n");
1.1 harris41 163: }
164:
165: # And does user already exist
166:
1.7 harris41 167: my $caveat =
168: 'For security reasons, this script will only automatically generate '."\n".
169: 'new users, not pre-existing users.'."\n".
170: "If you want to make '$username' a domain coordinator, you "."\n".
171: 'should do so manually by customizing the MANUAL PROCEDURE'."\n".
172: 'described in the documentation. To view the documentation '."\n".
173: 'for this script, type '.
174: "'perldoc ./make_domain_coordinator.pl'."."\n";
175:
1.1 harris41 176: if (-d "/home/$username") {
1.7 harris41 177: die ('**** ERROR **** '.$username.' is already a linux operating system '.
178: 'user.'."\n".$caveat);
1.1 harris41 179: }
1.10 albertel 180: my $udpath=&propath($domain,$username);
1.1 harris41 181: if (-d $udpath) {
1.7 harris41 182: die ('**** ERROR **** '.$username.' is already defined as a LON-CAPA '.
183: 'user.'."\n".$caveat);
1.1 harris41 184: }
185:
186: =pod
187:
188: =head1 MANUAL PROCEDURE
189:
1.7 harris41 190: There are 10 steps to manually recreating what this script performs
191: automatically.
1.1 harris41 192:
193: You need to decide on three pieces of information
194: to create a domain coordinator.
195:
196: * USERNAME (kermit, albert, joe, etc)
1.6 harris41 197: * DOMAIN (should be the same as lonDefDomain in /etc/httpd/conf/loncapa.conf)
1.1 harris41 198: * PASSWORD (don't tell me)
199:
200: The examples in these instructions will be based
201: on three example pieces of information:
202:
203: * USERNAME=dc103
204: * DOMAIN=103
205: * PASSWORD=sesame
206:
207: You will also need to know your "root" password
208: and your "www" password.
209:
210: =over 4
211:
212: =item 1.
213:
214: login as root on your Linux system
215: [prompt %] su
216:
217: =cut
218:
219: # ------------------------------------------------------------ So, are we root?
220:
1.7 harris41 221: if ($< != 0) { # Am I root?
1.1 harris41 222: die 'You must be root in order to generate a domain coordinator.'."\n";
223: }
224:
225: =pod
226:
227: =item 2 (as root). add the user
228:
229: Command: [prompt %] /usr/sbin/useradd USERNAME
230: Example: [prompt %] /usr/sbin/useradd dc103
231:
232: =cut
233:
1.11 raeburn 234: # ----------------------------------------------------------- /usr/sbin/groupadd
235: # -- Add group
236: $username=~s/\W//g; # an extra filter, just to be sure
237:
238: print "adding group: $username \n";
239: my $status = system('/usr/sbin/groupadd', $username);
240: if ($status) {
241: die "Error. Something went wrong with the addition of group ".
242: "\"$username\".\n";
243: }
244: my $gid = getgrnam($username);
245:
1.1 harris41 246: # ----------------------------------------------------------- /usr/sbin/useradd
1.11 raeburn 247: # -- Add user
1.1 harris41 248:
1.11 raeburn 249: print "adding user: $username \n";
250: my $status = system('/usr/sbin/useradd','-c','LON-CAPA user','-g',$gid,$username);
251: if ($status) {
252: system("/usr/sbin/groupdel $username");
253: die "Error. Something went wrong with the addition of user ".
254: "\"$username\".\n";
255: }
256:
257: print "Done adding user\n";
258: # Make www a member of that user group.
259: my $groups=`/usr/bin/groups www`;
260: # untaint
261: my ($safegroups)=($groups=~/:\s*([\s\w]+)/);
262: $groups=$safegroups;
263: chomp $groups; $groups=~s/^\S+\s+\:\s+//;
264: my @grouplist=split(/\s+/,$groups);
265: my @ugrouplist=grep {!/www|$username/} @grouplist;
266: my $gl=join(',',(@ugrouplist,$username));
267: print "Putting www in user's group\n";
268: if (system('/usr/sbin/usermod','-G',$gl,'www')) {
269: die "Error. Could not make www a member of the group ".
270: "\"$username\".\n";
271: }
272:
273: # Check if home directory exists for user
274: # If not, create one.
275: if (!-e "/home/$username") {
276: if (!mkdir("/home/$username",0710)) {
277: print "Error. Could not add home directory for ".
278: "\"$username\".\n";
279: }
280: }
1.1 harris41 281:
1.11 raeburn 282: if (-d "/home/$username") {
283: system('/bin/chown',"$username:$username","/home/$username");
284: system('/bin/chmod','-R','0660',"/home/$username");
285: system('/bin/chmod','0710',"/home/$username");
286: }
1.1 harris41 287: =pod
288:
289: =item 3 (as root). enter in a password
290:
291: Command: [prompt %] passwd USERNAME
292: New UNIX password: PASSWORD
293: Retype new UNIX passwd: PASSWORD
294: Example: [prompt %] passwd dc103
295: New UNIX password: sesame
296: Retype new UNIX passwd: sesame
297:
298: =cut
299:
1.7 harris41 300: # Process password (taint-check, then pass to the UNIX passwd command).
301: $username =~ s/\W//g; # an extra filter, just to be sure
302: $pbad = 0;
1.1 harris41 303: foreach (split(//,$passwd)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
304: if ($pbad) {
1.7 harris41 305: die('Password must consist of standard ASCII characters'."\n");
1.1 harris41 306: }
1.16 ! raeburn 307:
! 308: my $distro;
! 309: if (open(PIPE,"perl distprobe|")) {
! 310: $distro = <PIPE>;
! 311: close(PIPE);
! 312: }
! 313: if ($distro =~ /^ubuntu|debian/) {
! 314: open(OUT,"|usermod -p `mkpasswd $passwd` $username");
! 315: close(OUT);
! 316: } else {
! 317: open(OUT,"|passwd --stdin $username");
! 318: print(OUT $passwd."\n");
! 319: close(OUT);
! 320: }
1.1 harris41 321:
322: =pod
323:
324: =cut
325:
326: =pod
327:
328: =item 4. login as user=www
329:
330: Command: [prompt %] su www
331: Password: WWWPASSWORD
332:
333: =item 5. (as www). cd /home/httpd/lonUsers
334:
335: =item 6. (as www) Create user directory for your new user.
336:
337: Let U equal first letter of USERNAME
338: Let S equal second letter of USERNAME
339: Let E equal third letter of USERNAME
340: Command: [prompt %] install -d DOMAIN/U/S/E/USERNAME
1.7 harris41 341:
342: Here are three examples of the commands that would be needed
343: for different domain coordinator names (dc103, morphy, or ng):
344:
345: Example #1 (dc103): [prompt %] install -d 103/d/c/1/dc103
346: Example #2 (morphy): [prompt %] install -d 103/m/o/r/morphy
347: Example #3 (ng): [prompt %] install -d 103/n/g/_/ng
1.1 harris41 348:
349: =cut
350:
1.7 harris41 351: # Generate the user directory.
352: `install -o www -g www -d $udpath`; # Must be writeable by httpd process.
1.1 harris41 353:
354: =pod
355:
356: =item 7. (as www) Enter the newly created user directory.
357:
358: Command: [prompt %] cd DOMAIN/U/S/E/USERNAME
359: Example: [prompt %] cd 103/d/c/1/dc103
360:
361: =item 8. (as www). Set your password mechanism to 'unix'
362:
363: Command: [prompt %] echo "unix:" > passwd
364:
365: =cut
366:
1.7 harris41 367: # UNIX (/etc/passwd) style authentication is asserted for domain coordinators.
368: open(OUT, ">$udpath/passwd");
369: print(OUT 'unix:'."\n");
370: close(OUT);
1.15 www 371:
372: # Get permissions correct on udpath
373:
374: print "Setting permissions on user data directories. This may take a moment, please be patient ...\n";
375: `chown -R www:www /home/httpd/lonUsers/$domain` ; # Must be writeable by httpd process.
1.1 harris41 376:
377: =pod
378:
379: =item 9. (as www). Run CVS:loncapa/doc/rolesmanip.pl:
380:
381: Command: [prompt %] perl rolesmanip.pl DOMAIN USERNAME
382: Example: [prompt %] perl rolesmanip.pl 103 dc103
383:
384: =cut
385:
1.7 harris41 386: use GDBM_File; # A simplistic key-value pairing database.
1.1 harris41 387:
1.10 albertel 388: my $rolesref=&LONCAPA::locking_hash_tie("$udpath/roles.db",&GDBM_WRCREAT());
389: if (!$rolesref) {
390: die('unable to tie roles db: '."$udpath/roles.db");
391: }
1.13 raeburn 392: my $now = time;
393: $rolesref->{'/'.$domain.'/_dc'}='dc_0_'.$now; # Set the domain coordinator role.
1.7 harris41 394: open(OUT, ">$udpath/roles.hist"); # roles.hist is the synchronous plain text.
1.10 albertel 395: foreach my $key (keys(%{$rolesref})) {
396: print(OUT $key.' : '.$rolesref->{$key}."\n");
397: }
1.7 harris41 398: close(OUT);
1.10 albertel 399: &LONCAPA::locking_hash_untie($rolesref);
400:
1.1 harris41 401:
1.7 harris41 402: `chown www:www $udpath/roles.hist`; # Must be writeable by httpd process.
403: `chown www:www $udpath/roles.db`; # Must be writeable by httpd process.
1.1 harris41 404:
1.13 raeburn 405: my %perlvar = %{&LONCAPA::Configuration::read_conf('loncapa.conf')};
406: my $dompath = $perlvar{'lonUsersDir'}.'/'.$domain;
407: my $domrolesref = &LONCAPA::locking_hash_tie("$dompath/nohist_domainroles.db",&GDBM_WRCREAT());
408:
409: if (!$domrolesref) {
410: die('unable to tie nohist_domainroles db: '."$dompath/nohist_domainroles.db");
411: }
412:
413: # Store in nohist_domainroles.db
414: my $domkey=&LONCAPA::escape('dc:'.$username.':'.$domain.'::'.$domain.':');
415: $domrolesref->{$domkey}= &LONCAPA::escape('0:'.$now);
416: &LONCAPA::locking_hash_untie($domrolesref);
417:
1.14 raeburn 418: system('/bin/chown',"www:www","$dompath/nohist_domainroles.db"); # Must be writeable by httpd process.
419: system('/bin/chown',"www:www","$dompath/nohist_domainroles.db.lock");
420:
1.13 raeburn 421: #Update allusers MySQL table
422:
423: print "Adding new user to allusers table\n";
424: &allusers_update($username,$domain,\%perlvar);
425:
1.1 harris41 426: =pod
427:
428: =item 10.
429:
430: You may further define the domain coordinator user (i.e. dc103)
431: by going to http://MACHINENAME/adm/createuser.
432:
433: =cut
434:
1.7 harris41 435: # Output success message, and inform sysadmin about how to further proceed.
1.13 raeburn 436: print("\n$username is now a domain coordinator\n"); # Output success message.
1.7 harris41 437: my $hostname=`hostname`; chomp($hostname); # Read in hostname.
1.13 raeburn 438: print("\n".'Once LON-CAPA is running, you should log-in and use: '."\n".
439: 'http://'.$hostname.'/adm/createuser to further define this user.'."\n\n".
440: 'From the user management menu, click the link: "Add/Modify a Single User" '."\n".
441: 'to search for the user and to provide additional information (last name, first name etc.).'."\n");
442: # Output a suggested URL.
443:
444: sub allusers_update {
445: my ($username,$domain,$perlvar) = @_;
446: my %tablenames = (
447: 'allusers' => 'allusers',
448: );
449: my $dbh;
450: unless ($dbh = DBI->connect("DBI:mysql:loncapa","www",
451: $perlvar->{'lonSqlAccess'},
452: { RaiseError =>0,PrintError=>0})) {
453: print "Cannot connect to database!\n";
454: return;
455: }
456: my $tablechk = &allusers_table_exists($dbh);
457: if ($tablechk == 0) {
458: my $request =
459: &LONCAPA::lonmetadata::create_metadata_storage('allusers','allusers');
460: $dbh->do($request);
461: if ($dbh->err) {
462: print "Failed to crate allusers table\n";
463: return;
464: }
465: }
466: my %userdata = (
467: username => $username,
468: domain => $domain,
469: );
470: my %loghash =
471: &LONCAPA::lonmetadata::process_allusers_data($dbh,undef,
472: \%tablenames,$username,$domain,\%userdata,'update');
473: foreach my $key (keys(%loghash)) {
474: print $loghash{$key}."\n";
475: }
476: return;
477: }
478:
479: sub allusers_table_exists {
480: my ($dbh) = @_;
481: my $sth=$dbh->prepare('SHOW TABLES');
482: $sth->execute();
483: my $aref = $sth->fetchall_arrayref;
484: $sth->finish();
485: if ($sth->err()) {
486: return undef;
487: }
488: my $result = 0;
489: foreach my $table (@{$aref}) {
490: if ($table->[0] eq 'allusers') {
491: $result = 1;
492: last;
493: }
494: }
495: return $result;
496: }
1.1 harris41 497:
498: =pod
499:
1.2 harris41 500: =head1 AUTHOR
1.1 harris41 501:
1.7 harris41 502: Written to help the LON-CAPA project.
1.1 harris41 503:
504: =cut
1.13 raeburn 505:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>