add-users

  1: #!/bin/bash -
  2: # Script to add many student accounts at once.  This is the
  3: # Fedora version.  Run as root only.
  4: # See also the "disable-user" script to disable accounts before
  5: # permanently removing them with 'remove-user'.
  6: # TODO: currently invalid inputs abort program.  This should be
  7: # changed to a loop to try 'n' times (say 3) before aborting.
  8: # TODO: Add option for setting account expire date (for ua users).
  9: # TODO: change email subject to include the prefix (class name)
 10: # TODO: Save passwd list in ~/tmp files.
 11: # TODO: Add better defaults for all options.
 12: # TODO: code review: POSIX has added many shell features recently.  The
 13: # script should also serve as a model.
 14: #
 15: #  $Id: add-users,v 1.11 2016/05/04 06:16:52 wpollock Exp $
 16: #
 17: # Originally written 2002 by Wayne Pollock, Tampa FL USA.
 18: 
 19: PATH=/bin:/sbin:/usr/bin:/usr/sbin
 20: 
 21: PROG=${0##*/}
 22: HOST=$(uname -n)
 23: INSTRUCTOR=""
 24: CREATE_ADDITIONAL_GRP="n"
 25: ADD_INST_TO_ADDITIONAL_GRP="n"
 26: ADD_INST_TO_STUTENT_GRPS="n"
 27: 
 28: md5crypt()
 29: {
 30:     printf '%s\n' "$1" |openssl passwd -1 -stdin
 31: }
 32: 
 33: # A fancy separator line with optional centered title,
 34: # of 68 characters ("-") wide.
 35: 
 36: drawLine()
 37: {
 38:     local num line title
 39:     title="$1"
 40:     let "num = ( 68 - ${#title} ) / 2"
 41:     line=$(perl -e "print \"-\" x $num;")
 42:     printf '\n%s' "$line"
 43:     [ "$1" ] && printf '%s' " $1 "
 44:     printf '%s\n\n' "$line"
 45:     echo
 46: }
 47: 
 48: getInst()
 49: {
 50:     local CURR_USER instructor
 51: 
 52:     # "id" doesn't always (?) report the real UID, so prefer "logname":
 53:     if [ -x /usr/bin/logname ]
 54:     then CURR_USER=`logname`
 55:     else CURR_USER=`id -run`
 56:     fi
 57: 
 58:     printf '%s' "What is your account name (default: $CURR_USER)? " >/dev/tty
 59:     read instructor
 60:     if [ "$instructor" = "" ]
 61:     then instructor=$CURR_USER
 62:     fi
 63:     if ! grep -e "^$instructor:" /etc/passwd >/dev/null 2>&1
 64:     then
 65:         echo "No user account \"$instructor\" found!" >&2
 66:         echo "Exiting..." >&2
 67:         exit 2
 68:     fi
 69:     echo "$instructor"
 70: }
 71: 
 72: if [ "`id -u`" != "0" ]
 73: then echo "You must be root to run this script.  (Try \"sudo $PROG\".)"
 74:     exit 1
 75: fi
 76: 
 77: drawLine "Class Account Creation Wizard"
 78: 
 79: cat <<\EOF
 80: Created accounts have the form <prefix><number>
 81: (e.g. "rab01", "rab02", ...
 82: 
 83: Enter class prefix(es) (e.g.: ua ub uc)
 84: EOF
 85: printf "A good choice is the first few letters of the instructor's name: "
 86: read CLASSES
 87: set -- $CLASSES
 88: if [ $# = 0 ]
 89: then    echo "No classes entered, good-bye!"
 90:         exit 1
 91: fi
 92: 
 93: drawLine "Number of Accounts"
 94: 
 95: echo "Enter number of accounts to create per class (e.g.: 25)."
 96: echo "(Note that one additional account (numbered \"00\") will be"
 97: echo "created for the instructor to use, as a demo for example.)"
 98: echo
 99: printf '%s' "How many student accounts should be created? "
100: read NUM JUNK
101: if [ -z "$NUM" ]
102: then    echo "No value entered, good-bye!"
103:         exit 1
104: fi
105: if [ "$JUNK" ]
106: then    echo "Only enter a single value, good-bye!"
107:         exit 1
108: fi
109: 
110: shopt -s extglob  # Allow extended pattern matching in bash
111: case "$NUM" in
112: +([0-9])) if [ "$NUM" -eq 0 ]
113:           then echo "You must create at least one account, good-bye!"
114:                exit 1
115:           elif [ "$NUM" -gt 40 ]
116:           then printf '%s' "$NUM are a lot of accounts, are you sure? "
117:                read ANS
118:                if [ "$ANS" != 'y' ]
119:                then  echo "Good-bye!"
120:                      exit 1
121:                fi
122:           fi
123:           ;;
124: *)        echo "You must enter a positive number, good-bye!"
125:           exit 1
126:           ;;
127: esac
128: 
129: drawLine "Additional Group Setup"
130: 
131: echo "An additional group can be created for each class, named"
132: echo "for the class prefix.  Each student can be added as a member"
133: echo "of this group, so students will be members of two groups."
134: echo
135: printf '%s' "Do you wish to create an additional group per class (y/n)? "
136: read answer
137: if [ "$answer" = "y" ]
138: then
139:     CREATE_ADDITIONAL_GRP="y"
140:     echo
141:     printf '%s' "Do you want to add yourself to this group as well (y/n)? "
142:     read answer
143:     if [ "$answer" = "y" ]
144:     then
145: 	INSTRUCTOR=$(getInst)
146: 	ADD_INST_TO_ADDITIONAL_GRP="y"
147:     fi
148: fi
149: 
150: drawLine "Instructor Access"
151: 
152: echo "The system umask of \"027\" allows full access by a file's"
153: echo "owner, read access by group members, and no access for others."
154: echo "By adding the instructor as a group member, for each student's"
155: echo "group (there is one group per account), the instructor is"
156: echo "given read access to all student files, facilitating grading and"
157: echo "support."
158: echo
159: echo "Configure accounts so the instructor has read access"
160: printf '%s' " to all created account home directories (y/n)? "
161: read answer
162: if [ "$answer" = "y" ]
163: then ADD_INST_TO_STUTENT_GRPS="y"
164:      [ -z "$INSTRUCTOR" ] && INSTRUCTOR=$(getInst)
165: fi
166: 
167: drawLine "Account Locking"
168: 
169: TODAY=$(date +%D)
170: echo "For security accounts are created locked (disabled)."
171: printf '%s' "Enter the date the accounts should be unlocked (default: \"$TODAY\"): "
172: read UnlockDate JUNK
173: if [ -z "$UnlockDate" ]
174: then UnlockDate=$TODAY
175: fi
176: if [ -n "$JUNK" ]
177: then echo "You must enter a date such as \"mm/dd/yy\".  Good-bye!"
178:      exit 1
179: fi
180: CANONIZED_DATE=$(date --date="$UnlockDate" +%D) || exit 2
181: 
182: # Check to make sure the date entered is not in the past:
183: Today_stamp=`date --date="$TODAY" +%s`
184: Canon_stamp=`date --date="$CANONIZED_DATE" +%s`
185: if [ "$Canon_stamp" -lt "$Today_stamp" ]
186: then
187:     echo "You must enter today's date, or some date in the future!"
188:     exit 2
189: fi
190: 
191: if [ "$TODAY" = "$CANONIZED_DATE" ]
192: then pwPrefix=""
193:      printf '\n%s\n' '    *** Accounts will be created unlocked (enabled)! ***'
194: else pwPrefix="!"
195:      echo "Accounts will be unlocked at midnight on $CANONIZED_DATE."
196: fi
197: 
198: drawLine "Initial Passwords"
199: 
200: cat <<\EOF
201: For highest security, accounts can be created with default
202: passwords.  If you chose this option the account names and
203: their passwords will be emailed to you.  If not, accounts
204: will not have passwords (users are be forced to change the
205: passwords when they first login, regardless).
206: EOF
207: printf '\n%s' 'Create accounts with initial passwords? (y/n)'
208: read REPLY
209: case "$REPLY" in
210:   [yY]*) GenPW=yes
211: 	 echo
212:          printf '%s' "Enter email address to send password report to: "
213:          read INST_EMAIL JUNK
214:          echo "Passwords will be sent to $INST_EMAIL"
215:          ;;
216:   *)     GenPW=no
217:          echo "No passwords will be generated!"
218:          ;;
219: esac
220: 
221: echo
222: if [ "$NUM" -lt 10 ]
223: then DISP_NUM="0$NUM"
224: else DISP_NUM="$NUM"
225: fi
226: 
227: drawLine "Disk Quotas"
228: 
229: QUOTA_SOFT=6000  # 6 MiB
230: QUOTA_HARD=12000 # 12 MiB
231: # note file quotas, and all quotas for /var and /tmp are not
232: # configurable in this script!
233: 
234: echo Disk quotas are set for /home, /var, and /tmp for all users.
235: echo The defaults should be fine for most users but some may require
236: echo more space for their home directories.
237: echo
238: printf 'Use larger quotas for /home? (y/N) '
239: read answer
240: if [ "$answer" = "y" ]
241: then
242:   QUOTA_SOFT=25000000 # 25 GiB
243:   QUOTA_HARD=35000000 # 35 GiB
244: fi
245: 
246: drawLine "Configuration Summary"
247: 
248: # Show summary information for confirmation:
249: 
250: if [ $# -gt 1 ]
251: then
252:     printf '%s' "Creating $# classes of $((NUM + 1)) accounts each "
253:     echo "(\"${1}00\" - \"${1}$DISP_NUM\", ...)."
254: else
255:     printf '%s' "Creating 1 class of $((NUM + 1)) accounts "
256:     echo "(\"${1}00\" - \"${1}$DISP_NUM\")."
257: fi
258: 
259: echo "With quotas of"
260: printf '%-12s %-12s %-12s %-12s %-12s\n' \
261:  'Filesystem' 'Blocks-soft' 'Blocks-hard' 'Files-soft' 'Files-hard'
262: printf '%-12s %-12s %-12s %-12s %-12s\n' \
263:  '/home' "$QUOTA_SOFT" "$QUOTA_HARD" 2000 3000
264: printf '%-12s %-12s %-12s %-12s %-12s\n' \
265:  '/var' 1000 1500 100 200
266: printf '%-12s %-12s %-12s %-12s %-12s\n' \
267:  '/tmp' 1000 1500 100 200
268: 
269: if [ "$pwPrefix" = "" ]
270: then echo "Accounts will be created unlocked."
271: else echo "Accounts will be unlocked at midnight on $CANONIZED_DATE."
272: fi
273: 
274: if [ "$GenPW" = "yes" ]
275: then echo "Password list will be emailed to \"$INST_EMAIL\"."
276: else echo "Accounts will NOT have initial passwords."
277: fi
278: 
279: if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
280: then echo "Creating an additional group per class."
281:      if [ "$ADD_INST_TO_ADDITIONAL_GRP" = "y" ]
282:      then echo "    $INSTRUCTOR will be included in this group."
283:      fi
284: fi
285: 
286: if [ "$ADD_INST_TO_STUTENT_GRPS" = "y" ]
287: then echo "$INSTRUCTOR will have read access to all account files."
288: fi
289: 
290: echo
291: printf "OK to proceed? (y/N)? "
292: read answer
293: if [ "$answer" != "y" ]
294: then
295:     echo "You ARE the weakest link...good-bye!"
296:     exit 1
297: fi
298: 
299: # Create pipe to email message (password list):
300: [ "$GenPW" = "yes" ] && \
301:     exec 3> >(/bin/mail -s "New $HOST Account Passwords" "$INST_EMAIL")
302: 
303: for CLASS in $CLASSES
304: do
305:     if grep "^$CLASS[0-9]" /etc/passwd >/dev/null 2>&1
306:     then echo "Class \"$CLASS\" already exists, skipping..."
307:          logger -p warn -t "add-users" \
308:              "Skipping account creation for $CLASS, already exists."
309:          continue
310:     fi
311: 
312:     logger -t "add-users" "Creating new user accounts with prefix $CLASS."
313: 
314:     if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
315:     then echo "Creating new group \"$CLASS\"..."
316:          groupadd "$CLASS"
317: 	 if [ "$ADD_INST_TO_ADDITIONAL_GRP" = "y" ]
318: 	 then
319: 	     gpasswd -a "$INSTRUCTOR" "$CLASS"
320: 	fi
321:     fi
322: 
323:     for i in $(seq -f "%02.0f" 0 $NUM)
324:     do
325:         User="$CLASS$i"
326:         printf '%s' "Adding user $User."
327: 
328:         # Create group account for user:
329:         groupadd "$User"
330:         printf '.'
331: 
332:         if [ "$ADD_INST_TO_STUTENT_GRPS" = "y" ]
333:         then
334:             gpasswd -a "$INSTRUCTOR" "$User" |tr '\n' '.'
335:         fi
336: 
337:         # Set the password: "" = no pw, unlocked, "!" = no pw, locked,
338:         # "Xy1Abc" = pw and unlocked, and "!Xy1Abc" = pw, locked.
339:         PASSWORD="$pwPrefix"
340:         if [ "$GenPW" = "yes" ]
341:         then
342:             # pronouncable with at least one digit and capital leter:
343:             pass=$(pwgen -cn1)
344:             crypt=$(md5crypt "$pass")
345:             PASSWORD="$pwPrefix$crypt"
346:             printf "$User\t$pass\n" >&3  # Append name, password to email.
347:         fi
348: 
349:         # Create user: -m means create home dir and copy /etc/skel,
350:         #              -p '!' means create NULL (no) password for Linux
351:         #                 but initially lock (disable) the account,
352:         #              -g $User means put the user in his/her own group
353: 
354:         useradd -m -p "$PASSWORD" -g "$User" -K UMASK=027 "$User"
355:         printf '.'
356: 
357:         # Add the user to group "tty".  This is needed on some systems
358:         # (such as Fedora 21) before users can use write, wall, talk:
359:         gpasswd -a "$User" tty
360:         printf '.'
361: 
362:         # This forces the password to be change every 150 days, and
363:         # sets the date of last change to be 1/1/1970.  Thus a user
364:         # is forced to set a password the first time they log in, and
365:         # that password should be valid for one semester (plus a few
366:         # extra weeks).
367:         chage -M 150 -d 1 "$User"  #Linux cmd, Solaris uses passwd
368:         printf '.'
369: 
370:         # Set the quotas for the user:
371:         setquota -u "$User" $QUOTA_SOFT $QUOTA_HARD 2000 3000 /home
372:         setquota -u "$User" 1000 1500 100 200 /var
373:         setquota -u "$User" 1000 1500 100 200 /tmp
374:         printf '.'
375: 
376: 	# Add user to additional group:
377: 	if [ "$CREATE_ADDITIONAL_GRP" = "y" ]
378: 	then
379: 	    gpasswd -a "$User" "$CLASS" >/dev/null
380:             printf '.'
381: 	fi
382: 
383:         echo "done!"
384:     done
385: done
386: 
387: # Close the pipe to mail (which will send the email):
388: [ "$GenPW" = "yes" ] && exec 3>&-
389: 
390: # Create an "at" job to unlock accounts on the specified date
391: # if necessary:
392: if [ "$pwPrefix" = '!' ]
393: then
394: at midnight $CANONIZED_DATE >/dev/null 2>&1 <<-EOF
395: 	for c in $CLASSES
396: 	do
397: 	    for i in `seq -s ' ' -f "%02.0f" 0 $NUM`
398: 	    do
399: 	        usermod -U "\$c\$i"
400: 	    done
401: 	done >/dev/null 2>&1
402: 	logger -t "add-users" "Enabling $CLASSES student accounts."
403: 	EOF
404: fi