At one of our customer we will have an monstre’ UID-consolidation projekt in the near future. For this purpose I’ve written a script to enlighten our task. Most of you thinks at it a an over-complicated (usermod && find …. chown …) job, but my script has undergone some performance optimisation too: it starts parallel (find….chown) processes to get the job done as fast as it can be. Here is the output for changing the UID of a testuser:
# ./chguid testuser 234 Checking parameters OK Checking for duplicate UID OK Changing the UID in the password database OK The UID was successfully changed: Before: testuser:x:123:20:test:/home/testuser:/usr/bin/ksh After: testuser:x:234:20:test:/home/testuser:/usr/bin/ksh Starting parallel processes to change ownership Threads with chown: (the list may be incomplete on fast systems) root 4993 4981 7 13:54:04 pts/0 0:00 find / -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + root 4995 1 7 13:54:04 pts/0 0:00 find /orae6/hes1 -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + root 4994 1 5 13:54:04 pts/0 0:00 find /orae6/heb1 -xdev -user 123 ( -type f -o -type d ) -exec chown 234 + FINISHED #
and here is the script:
#!/usr/bin/ksh
#
# Author: Viktor Balogh
# Version: v0.4
#
function usage {
print "\n Usage: chguid USERNAME NEW_UID\n"
print " Where USERNAME is the name of an existing user and UID is between 0 and 2147483647."
print " This UID needs to be unique, as this will be the new UID of the user.\n"
print " The UID of the system users cannot be changed for security reasons.\n"
}
function check_root {
# check if we have enough permission
if [ $(id -u) -ne "0" ]
then
printf " %s\n\n" "You should be root in order to use this tool!"
exit 6
fi
}
function check_param {
# check the parameters of the script
printf " %-60s" "Checking parameters"
if [ $# -ne 2 ]
then
printf "%s\n" "ERROR"
printf " %s\n\n" "Please feed exactly two parameters!"
usage
exit 2
elif $(pwget -n ${UNAME} 1>/dev/null 2>/dev/null)
then
# UID_MAX is 2147483647 in limits.h, so 2147483646 is the highest UID
# we do not touch UID 0 and system users
if [[ "${UID}" -lt "2147483647" && "${UID}" -gt "0" && "${UNAME}" != "root" &&
"${UNAME}" != "daemon" && "${UNAME}" != "bin" && "${UNAME}" != "sys" &&
"${UNAME}" != "adm" && "${UNAME}" != "uucp" && "${UNAME}" != "lp" &&
"${UNAME}" != "nuucp" && "${UNAME}" != "hpdb" && "${UNAME}" != "nobody" ]]
then
# check if UID already exists
printf "%s\n" "OK"
printf " %-60s" "Checking for duplicate UID"
UID_SEARCH=$(pwget | awk -F: '$3 == "'${UID}'"' 2>/dev/null)
if [ ! -z "${UID_SEARCH}" ]
then
printf "%s\n" "ERROR"
printf " %s\n\n" "The given UID already exists! Please specify another one!"
usage
exit 5
else
printf "%s\n" "OK"
fi
else
printf "%s\n" "ERROR"
printf " %s\n\n" "Invalid UID/UNAME!"
usage
exit 4
fi
else
printf "%s\n" "ERROR"
printf " %s\n\n" "User not found!"
exit 3
fi
}
function check_trusted {
## OBSOLETE : usermod also handles trusted
# check if system is trusted
printf " %-60s" "Checking if system is trusted"
/usr/lbin/getprdef -r 1>/dev/null 2>/dev/null
if [ $? -eq 4 ]
then
printf "%s\n" "NO"
else
printf "%s\n" "YES"
printf " %s\n" "Trusted mode is on, but this hasn't been implemented in this script. Contact developer!"
exit 1
fi
}
function check_nis {
## OBSOLETE : pwget/usermod also handles NIS
# check if NIS is in use
printf " %-60s" "Checking if NIS is used for authentication"
/usr/bin/ypwhich 1>/dev/null 2>/dev/null
if [ $? -eq 1 ]
then
NIS=0
printf "%s\n" "NO"
else
NIS=1
printf "%s\n" "YES"
printf " %s\n" "NIS is in use, but this hasn't been implemented in this script. Contact developer!"
exit 1
fi
}
function check_shadow {
## OBSOLETE : pwget/usermod also handles ShadowPassword
# check if ShadowPassword is in use
printf " %-60s" "Checking if passwords are shadow'ed"
if [ -f /etc/shadow ]
then
SHADOW=1
printf "%s\n" "YES"
else
SHADOW=0
printf "%s\n" "NO"
fi
}
function change_uid {
# before any modification make a backup of the passwd/shadow file and keep the old value of the UID
POSTFIX=$(date '+%d%m%Y_%H%M%S')
OLDUID=$(pwget -n ${UNAME} | cut -d: -f3)
if [ -f /etc/passwd ]
then
cp /etc/passwd /etc/passwd_${POSTFIX}
fi
if [ -f /etc/shadow ]
then
cp /etc/shadow /etc/shadow_${POSTFIX}
fi
# changing the UID with the usermod command
printf " %-60s" "Changing the UID in the password database"
CHECK_BEFORE="$(pwget | awk -F: '$1 == "'${UNAME}'" && $3 == "'${OLDUID}'"')"
usermod -u ${UID} ${UNAME} 1>/dev/null 2>/dev/null
# The following is an extended check because usermod fails in some strange cases if the permission of the homedir is 700 or so.
# Only the password entry should be checked, the homedir can be ignored as the massive find/chown operation begins only after this.
# That's why we cannot rely on the exit status of usermod.
CHECK_AFTER="$(pwget | awk -F: '$1 == "'${UNAME}'" && $3 == "'${UID}'"')"
if [[ $? -eq "0" || ! -z "${CHECK_AFTER}" ]]
then
printf "%s\n" "OK"
printf " %-60s\n\n" "The UID was successfully changed:"
printf " %-60s\n" "Before:"
printf " %-60s\n" "${CHECK_BEFORE}"
printf " %-60s\n" "After:"
printf " %-60s\n" "${CHECK_AFTER}"
print
else
printf "%s\n" "ERROR"
printf " %-60s\n" "The change of the UID was failed, for more info try it manually:"
printf " %-60s\n" "usermod -u ${UID} ${UNAME}"
printf " %-60s\n" "...and do not forget to chown all the data to the new UID!"
exit 7
fi
}
function change_ownership {
# starting some find processes to change the ownership
printf " %-60s\n" "Starting parallel processes to change ownership"
# Now we start one find process on each separate NFS mount...
for nfs in $(mount -v | awk '/ type nfs / {print $3}')
do
find ${nfs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} {} \+ &
done &
# only a single find process on the local disks...
for localfs in $(mount -v | awk '$1 ~ /\/dev\/vg0[01]/ {print $3}')
do
find ${localfs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} \+
done &
# ...and one find per FS on storage
for storagefs in $(mount -v | awk '$1 !~ /\/dev\/vg0[01]/ && $5 !~ /nfs/ {print $3}')
do
find ${storagefs} -xdev -user ${OLDUID} \( -type f -o -type d \) -exec chown ${UID} \+ &
done &
printf " %-60s\n\n" "Threads with chown: (the list may be incomplete on fast systems)"
ps -ef | awk '(/chown/ || /find/) && ! /awk/'
wait
printf "\n %s\n" "FINISHED"
}
print
UNAME=$1
UID=$2
check_root
check_param ${UNAME} ${UID}
change_uid
change_ownership
print
It needs some customization if you want to use it in your environment, because it assesses the type/location of the FS based on the name of the volumgroup. Feel free to comment & suggest modifications!
UPDATE 10.12.2010: Thanks to Markus, the testuser-bug was corrected, v0.4 released.
3 Comments
Great script. I have a project coming up where parts of this will prove useful, thanks!
I had to correct one line:
From:
OLDUID=$(pwget -n testuser | cut -d: -f3)
To:
OLDUID=$(pwget -n ${UNAME} | cut -d: -f3)
But apart from that everything worked fine! Thanks for the great script!
Hello Markus,
thanks for the correction, I have updated the post. After all, I also corrected the formatting of the script, because it was screwed by the WYSIWIG editor of WordPress.
Regards,
Viktor