File:
[LON-CAPA] /
loncom /
Attic /
lcuseradd
Revision
1.38:
download - view:
text,
annotated -
select for diffs
Fri Jul 29 17:33:18 2005 UTC (19 years, 1 month ago) by
raeburn
Branches:
MAIN
CVS tags:
version_2_2_0,
version_2_1_X,
version_2_1_99_3,
version_2_1_99_2,
version_2_1_99_1,
version_2_1_99_0,
version_2_1_3,
version_2_1_2,
version_2_1_1,
version_2_1_0,
version_2_0_X,
version_2_0_99_1,
version_2_0_2,
version_2_0_1,
version_2_0_0,
version_1_99_3,
HEAD
Changes to accommodate suse, where new users are put into default group rather than into new group with same name as username. Solve this by creating group first, before adding user. Also create user's home directory if not created automatically.
1: #!/usr/bin/perl
2:
3: # The Learning Online Network with CAPA
4: #
5: # lcuseradd - LON-CAPA setuid script to coordinate all actions
6: # with adding a user with filesystem privileges (e.g. author)
7: #
8: #
9: # $Id: lcuseradd,v 1.38 2005/07/29 17:33:18 raeburn Exp $
10: ###
11:
12: ###############################################################################
13: ## ##
14: ## ORGANIZATION OF THIS PERL SCRIPT ##
15: ## ##
16: ## 1. Description of script ##
17: ## 2. Invoking script (standard input) ##
18: ## 3. Example usage inside another piece of code ##
19: ## 4. Description of functions ##
20: ## 5. Exit codes ##
21: ## 6. Initializations ##
22: ## 7. Make sure this process is running from user=www ##
23: ## 8. Start running script with www permissions ##
24: ## 9. Handle case of another lcpasswd process (locking) ##
25: ## 10. Error-check input, need 3 values (user name, password 1, password 2) ##
26: ## 11. Start running script with root permissions ##
27: ## 12. Add user and make www a member of the user-specific group ##
28: ## 13. Set password ##
29: ## 14. Make final modifications to the user directory ##
30: ## 15. Exit script (unlock) ##
31: ## ##
32: ###############################################################################
33:
34: use strict;
35: use File::Find;
36:
37:
38: # ------------------------------------------------------- Description of script
39: #
40: # This script is a setuid script that should
41: # be run by user 'www'. It creates a /home/USERNAME directory.
42: # It adds a user to the unix system.
43: # Passwords are set with lcpasswd.
44: # www becomes a member of this user group.
45:
46: # -------------- Invoking script (standard input versus command-line arguments)
47: # Otherwise sensitive information will be available to ps-ers for
48: # a small but exploitable time window.
49: #
50: # Standard input (STDIN) usage
51: # First line is USERNAME
52: # Second line is PASSWORD
53: # Third line is PASSWORD
54: # Fouth line is the name of a file to which an error code will be written.
55: # If the fourth line is omitted, no error file will be written.
56: # In either case, the program Exits with the code as its Exit status.
57: # The error file will just be a single line containing an
58: # error code.
59: #
60: #
61: #
62: # Command-line arguments [USERNAME] [PASSWORD] [PASSWORD]
63: # Yes, but be very careful here (don't pass shell commands)
64: # and this is only supported to allow perl-system calls.
65: #
66: # Valid passwords must consist of the
67: # ascii characters within the inclusive
68: # range of 0x20 (32) to 0x7E (126).
69: # These characters are:
70: # SPACE and
71: # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNO
72: # PQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
73: #
74: # Valid user names must consist of ascii
75: # characters that are alphabetical characters
76: # (A-Z,a-z), numeric (0-9), or the underscore
77: # mark (_). (Essentially, the perl regex \w).
78: # User names must begin with an alphabetical character
79: # (A-Z,a-z).
80:
81: # ---------------------------------- Example usage inside another piece of code
82: # Usage within code
83: #
84: # $Exitcode=
85: # system("/home/httpd/perl/lcuseradd","NAME","PASSWORD1","PASSWORD2")/256;
86: # print "uh-oh" if $Exitcode;
87:
88: # ---------------------------------------------------- Description of functions
89: # enable_root_capability() : have setuid script run as root
90: # disable_root_capability() : have setuid script run as www
91: # try_to_lock() : make sure that another lcpasswd process isn't running
92:
93: # ------------------------------------------------------------------ Exit codes
94: # These are the Exit codes.
95: # ( (0,"ok"),
96: # (1,"User ID mismatch. This program must be run as user 'www'"),
97: # (2,"Error. This program needs 3 command-line arguments (username, ".
98: # "password 1, password 2)."),
99: # (3,"Error. Three lines should be entered into standard input."),
100: # (4,"Error. Too many other simultaneous password change requests being ".
101: # "made."),
102: # (5,"Error. User $username does not exist."),
103: # (6,"Error. Could not make www a member of the group \"$safeusername\"."),
104: # (7,"Error. Root was not successfully enabled.),
105: # (8,"Error. Cannot set password."),
106: # (9,"Error. The user name specified has invalid characters."),
107: # (10,"Error. A password entry had an invalid character."),
108: # (11,"Error. User already exists.),
109: # (12,"Error. Something went wrong with the addition of user ".
110: # "\"$safeusername\"."),
111: # (13,"Error. Password mismatch."),
112: # (14, "Error filename is invalid"),
113: # (15, "Error. Could not add home directory.")
114:
115: # ------------------------------------------------------------- Initializations
116: # Security
117: $ENV{'PATH'}='/bin/:/usr/bin:/usr/local/sbin:/home/httpd/perl'; # Nullify path
118: # information
119: delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # nullify potential taints
120:
121: # Do not print error messages.
122: my $noprint=1;
123:
124: # Error file:
125:
126: my $error_file; # This is either the error file name or undef.
127:
128: print "In lcuseradd\n" unless $noprint;
129:
130: # ----------------------------- Make sure this process is running from user=www
131: my $wwwid=getpwnam('www');
132: &disable_root_capability;
133: if ($wwwid!=$>) {
134: print("User ID mismatch. This program must be run as user 'www'\n")
135: unless $noprint;
136: &Exit(1);
137: }
138:
139: # ----------------------------------- Start running script with www permissions
140: &disable_root_capability;
141:
142: # --------------------------- Handle case of another lcpasswd process (locking)
143: unless (&try_to_lock("/tmp/lock_lcpasswd")) {
144: print "Error. Too many other simultaneous password change requests being ".
145: "made.\n" unless $noprint;
146: &Exit(4);
147: }
148:
149: # ------- Error-check input, need 3 values (user name, password 1, password 2).
150: my @input;
151: if (@ARGV>=3) {
152: @input=@ARGV;
153: } elsif (@ARGV) {
154: print("Error. This program needs at least 3 command-line arguments (username, ".
155: "password 1, password 2 [errorfile]).\n") unless $noprint;
156: unlink('/tmp/lock_lcpasswd');
157: &Exit(2);
158: } else {
159: @input=<>;
160: if (@input < 3) {
161: print("Error. At least three lines should be entered into standard input.\n")
162: unless $noprint;
163: unlink('/tmp/lock_lcpasswd');
164: &Exit(3);
165: }
166: foreach (@input) {chomp;}
167: }
168:
169: my ($username,$password1,$password2, $error_file)=@input;
170: print "Username = ".$username."\n" unless $noprint;
171: $username=~/^(\w+)$/;
172: print "Username after substitution - ".$username unless $noprint;
173: my $safeusername=$1;
174: print "Safe username = $safeusername \n" unless $noprint;
175:
176: if (($username ne $safeusername) or ($safeusername!~/^[A-Za-z]/)) {
177: print "Error. The user name specified $username $safeusername has invalid characters.\n"
178: unless $noprint;
179: unlink('/tmp/lock_lcpasswd');
180: &Exit(9);
181: }
182: my $pbad=0;
183: foreach (split(//,$password1)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
184: foreach (split(//,$password2)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}}
185: if ($pbad) {
186: print "Error. A password entry had an invalid character.\n" unless $noprint;
187: unlink('/tmp/lock_lcpasswd');
188: &Exit(10);
189: }
190:
191: #
192: # Safe the filename. For our case, it must only have alpha, numeric, period
193: # and path sparators..
194: #
195:
196: print "Error file is $error_file \n" unless $noprint;
197:
198: if($error_file) {
199: if($error_file =~ /^([(\w)(\d)\.\/]+)$/) {
200: print "Error file matched pattern $error_file : $1\n" unless $noprint;
201: my $safe_error_file = $1; # Untainted I think.
202: print "Error file after transform $safe_error_file\n"
203: unless $noprint;
204: if($error_file == $safe_error_file) {
205: $error_file = $safe_error_file; # untainted error_file.
206: } else {
207: $error_file ="";
208: print "Invalid error filename\n" unless $noprint;
209: Exit(14);
210: }
211:
212: } else {
213: $error_file="";
214: print "Invalid error filename\n" unless $noprint;
215: Exit(14);
216: }
217: }
218:
219:
220: # -- Only add the user if they are >not< in /etc/passwd.
221: # Used to look for the ability to create a new directory for the
222: # user, however that disallows authentication changes from i
223: # internal->fs.. so just check the passwd file instead.
224: #
225: my $not_found = system("cut -d: -f1 /etc/passwd | grep -q \"^$safeusername\$\" ");
226: if (!$not_found) {
227: print "Error user already exists\n" unless $noprint;
228: unlink('/tmp/lock_lcpasswd');
229: &Exit(11);
230: }
231:
232:
233:
234: # -- Only add user if the two password arguments match.
235:
236: if ($password1 ne $password2) {
237: print "Error. Password mismatch.\n" unless $noprint;
238: unlink('/tmp/lock_lcpasswd');
239: &Exit(13);
240: }
241: print "enabling root\n" unless $noprint;
242: # ---------------------------------- Start running script with root permissions
243: &enable_root_capability;
244:
245: # ------------------- Add group and user, and make www a member of the group
246: # -- Add group
247:
248: print "adding group: $safeusername \n" unless $noprint;
249: my $status = system('/usr/sbin/groupadd', $safeusername);
250: if ($status) {
251: print "Error. Something went wrong with the addition of group ".
252: "\"$safeusername\".\n" unless $noprint;
253: print "Final status of groupadd = $status\n";
254: unlink('/tmp/lock_lcpasswd');
255: &Exit(12);
256: }
257: my $gid = getgrnam($safeusername);
258:
259: # -- Add user
260:
261: print "adding user: $safeusername \n" unless $noprint;
262: my $status = system('/usr/sbin/useradd','-c','LON-CAPA user','-g',$gid,$safeusername);
263: if ($status) {
264: print "Error. Something went wrong with the addition of user ".
265: "\"$safeusername\".\n" unless $noprint;
266: system("/usr/sbin/groupdel $safeusername");
267: print "Final status of useradd = $status\n";
268: unlink('/tmp/lock_lcpasswd');
269: &Exit(12);
270: }
271:
272: print "Done adding user\n" unless $noprint;
273: # Make www a member of that user group.
274: my $groups=`/usr/bin/groups www` or &Exit(6);
275: # untaint
276: my ($safegroups)=($groups=~/:\s*([\s\w]+)/);
277: $groups=$safegroups;
278: chomp $groups; $groups=~s/^\S+\s+\:\s+//;
279: my @grouplist=split(/\s+/,$groups);
280: my @ugrouplist=grep {!/www|$safeusername/} @grouplist;
281: my $gl=join(',',(@ugrouplist,$safeusername));
282: print "Putting www in user's group\n" unless $noprint;
283: if (system('/usr/sbin/usermod','-G',$gl,'www')) {
284: print "Error. Could not make www a member of the group ".
285: "\"$safeusername\".\n" unless $noprint;
286: unlink('/tmp/lock_lcpasswd');
287: &Exit(6);
288: }
289:
290: # ---------------------------------------------------------------- Set password
291: # Set password with lcpasswd (which creates smbpasswd entry).
292:
293: unlink('/tmp/lock_lcpasswd');
294: &disable_root_capability;
295: ($>,$<)=($wwwid,$wwwid);
296: print "Opening lcpasswd pipeline\n" unless $noprint;
297: open OUT,"|/home/httpd/perl/lcpasswd";
298: print OUT $safeusername;
299: print OUT "\n";
300: print OUT $password1;
301: print OUT "\n";
302: print OUT $password1;
303: print OUT "\n";
304: close OUT;
305: if ($?) {
306: print "abnormal Exit from close lcpasswd\n" unless $noprint;
307: &Exit(8);
308: }
309: ($>,$<)=($wwwid,0);
310: &enable_root_capability;
311:
312: # Check if home directory exists for user
313: # If not, create one.
314: if (!-e "/home/$safeusername") {
315: if (!mkdir("/home/$safeusername",0710)) {
316: print "Error. Could not add home directory for ".
317: "\"$safeusername\".\n" unless $noprint;
318: unlink('/tmp/lock_lcpasswd');
319: &Exit(15);
320: }
321: }
322:
323: # ------------------------------ Make final modifications to the user directory
324: # -- Add a public_html file with a stand-in index.html file
325:
326: if (-d "/home/$safeusername") {
327: system('/bin/chmod','-R','0660',"/home/$safeusername");
328: system('/bin/chmod','0710',"/home/$safeusername");
329: mkdir "/home/$safeusername/public_html",0755;
330: open OUT,">/home/$safeusername/public_html/index.html";
331: print OUT<<END;
332: <html>
333: <head>
334: <title>$safeusername</title>
335: </head>
336: <body>
337: <h1>Construction Space</h1>
338: <h3>$safeusername</h3>
339: </body>
340: </html>
341: END
342: close OUT;
343: }
344:
345: #
346: # In order to allow the loncapa daemons appropriate access
347: # to public_html, Top level and public_html directories should
348: # be owned by safeusername:safeusername as should the smaple index.html..
349: print "lcuseradd ownership\n" unless $noprint;
350: system('/bin/chown','-R',"$safeusername:$safeusername","/home/$safeusername"); # First set std ownership on everything.
351: &set_public_html_permissions("/home/$safeusername/public_html");
352: # system('/bin/chown',"$safeusername:www","/home/$safeusername"); # Now adust top level...
353: # system('/bin/chown','-R',"$safeusername:www","/home/$safeusername/public_html"); # And web dir.
354: # ---------------------------------------------------- Gracefull Apache Restart
355: if (-e '/var/run/httpd.pid') {
356: print "lcuseradd Apache restart\n" unless $noprint;
357: open(PID,'/var/run/httpd.pid');
358: my $pid=<PID>;
359: close(PID);
360: my $pid=~ /(\D+)/;
361: my $safepid = $1;
362: if ($pid) {
363: system('kill','-USR1',"$safepid");
364: }
365: }
366: # -------------------------------------------------------- Exit script
367: print "lcuseradd Exiting\n" unless $noprint;
368: &disable_root_capability;
369: &Exit(0);
370:
371: # ---------------------------------------------- Have setuid script run as root
372: sub enable_root_capability {
373: if ($wwwid==$>) {
374: ($<,$>)=($>,0);
375: ($(,$))=($),0);
376: } else {
377: # root capability is already enabled
378: }
379: return $>;
380: }
381:
382: # ----------------------------------------------- Have setuid script run as www
383: sub disable_root_capability {
384: if ($wwwid==$<) {
385: ($<,$>)=($>,$<);
386: ($(,$))=($),$();
387: } else {
388: # root capability is already disabled
389: }
390: }
391:
392: # ----------------------- Make sure that another lcpasswd process isn't running
393: sub try_to_lock {
394: my ($lockfile)=@_;
395: my $currentpid;
396: my $lastpid;
397: # Do not manipulate lock file as root
398: if ($>==0) {
399: return 0;
400: }
401: # Try to generate lock file.
402: # Wait 3 seconds. If same process id is in
403: # lock file, then assume lock file is stale, and
404: # go ahead. If process id's fluctuate, try
405: # for a maximum of 10 times.
406: for (0..10) {
407: if (-e $lockfile) {
408: open(LOCK,"<$lockfile");
409: $currentpid=<LOCK>;
410: close LOCK;
411: if ($currentpid==$lastpid) {
412: last;
413: }
414: sleep 3;
415: $lastpid=$currentpid;
416: } else {
417: last;
418: }
419: if ($_==10) {
420: return 0;
421: }
422: }
423: open(LOCK,">$lockfile");
424: print LOCK $$;
425: close LOCK;
426: return 1;
427: }
428: # Called by File::Find::find for each file examined.
429: #
430: # Untaint the file and, if it is a directory,
431: # chmod it to 02770
432: #
433: sub set_permission {
434: $File::Find::name =~ /^(.*)$/;
435: my $safe_name = $1; # Untainted filename...
436:
437: print "$safe_name" unless $noprint;
438: if(-d $safe_name) {
439: print " - directory" unless $noprint;
440: chmod(02770, $safe_name);
441: }
442: print "\n" unless $noprint;
443:
444: }
445: #
446: # Set up the correct permissions for all files in the
447: # user's public htmldir. We just do a chmod -R 0660 ... for
448: # the ordinary files. The we use File::Find
449: # to pop through the directory tree changing directories only
450: # to 02770:
451: #
452: sub set_public_html_permissions {
453: my ($topdir) = @_;
454:
455: # Set the top level dir permissions (I'm not sure if find
456: # will enumerate it specifically), correctly and all
457: # files and dirs to the 'ordinary' file permissions:
458:
459: system("chmod -R 0660 $topdir");
460: chmod(02770, $topdir);
461:
462: # Now use find to locate all directories under $topdir
463: # and set their modes to 02770...
464: #
465: print "Find file\n " unless $noprint;
466: File::Find::find({"untaint" => 1,
467: "untaint_pattern" => qr(/^(.*)$/),
468: "untaint_skip" => 1,
469: "no_chdir" => 1,
470: "wanted" => \&set_permission }, "$topdir");
471:
472:
473: }
474:
475: #-------------------------- Exit...
476: #
477: # Write the file if the error_file is defined. Regardless
478: # Exit with the status code.
479: #
480: sub Exit {
481: my ($code) = @_; # Status code.
482:
483: # TODO: Ensure the error file is owned/deletable by www:www:
484:
485: &disable_root_capability(); # We run unprivileged to write the error file.
486:
487: print "Exiting with status $code error file is $error_file\n" unless $noprint;
488: if($error_file) {
489: open(FH, ">$error_file");
490: print FH "$code\n";
491: close(FH);
492: }
493: exit $code;
494: }
495:
496: =head1 NAME
497:
498: lcuseradd - LON-CAPA setuid script to coordinate all actions
499: with adding a user with filesystem privileges (e.g. author)
500:
501: =head1 DESCRIPTION
502:
503: lcuseradd - LON-CAPA setuid script to coordinate all actions
504: with adding a user with filesystem privileges (e.g. author)
505:
506: =head1 README
507:
508: lcuseradd - LON-CAPA setuid script to coordinate all actions
509: with adding a user with filesystem privileges (e.g. author)
510:
511: =head1 PREREQUISITES
512:
513: =head1 COREQUISITES
514:
515: =pod OSNAMES
516:
517: linux
518:
519: =pod SCRIPT CATEGORIES
520:
521: LONCAPA/Administrative
522:
523: =cut
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>