A uid merger

Dan Stromberg - OAC-DCS (strombrg@bingy.acs.uci.edu)
Wed, 27 Jul 1994 21:49:49 -0700

This merges password databases. With a light preplanning, it'll do
home directories as well.

I'm not 100% happy with my hack of prepending a letter of the alphabet
to some of my keys, in order to get keys back in a particular
(non-alphabetic) order - but it seemed simpler than creating a class
and defining a comparison function.

Comments?

#!/bin/sh
# This is a shell archive (shar 3.32)
# made 07/28/1994 04:46 UTC by strombrg@bingy.acs.uci.edu
# Source directory /users/dcs/strombrg/security/passwd/uid-merge
#
# existing files WILL be overwritten
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 736 -rw-r--r-- README
# 4074 -rwxr-xr-x uid-merge
# 287 -rwxr-xr-x gen-paths
# 1028 -rwxr-xr-x chowns
#
if touch 2>&1 | fgrep 'amc' > /dev/null
then TOUCH=touch
else TOUCH=true
fi
# ============= README ==============
echo "x - extracting README (Text)"
sed 's/^X//' << 'SHAR_EOF' > README &&
X
XTo merge N password databases, create N files, one for each database,
XEG when merging two databases, create "YP1" and "YP2".
X
XThen create, probably employing "gen-paths", "YP1-paths" and "YP2-paths".
XThese are shell scripts that will be invoked to remap home directories
Xas needed. 'echo "$@"' is a minimal "-paths" script, if you don't want
Xto bother changing home directories.
X
XThe run "./uid-merge YP1 YP2". Output is probably pretty self-explanatory.
X
X"chowns" is a program for chowning many files, in a single pass over
Xa filesystem. It expects "olduid newuid" pairs on each line of stdin.
Xgrep for "chown" in uid-merge's output, and awk out the uid fields...
X
XPls feel free to send questions and patches to strombrg@uci.edu.
X
SHAR_EOF
$TOUCH -am 0727214694 README &&
chmod 0644 README ||
echo "restore of README failed"
set `wc -c README`;Wc_c=$1
if test "$Wc_c" != "736"; then
echo original size 736, current size $Wc_c
fi
# ============= uid-merge ==============
echo "x - extracting uid-merge (Text)"
sed 's/^X//' << 'SHAR_EOF' > uid-merge &&
X#!/dcs/bin/python
X
X# uid ----- YPdomain --- password database entry
X# \ \
X# \ -- password database entry
X# \
X# -- YPdomain --- password database entry
X# \
X# ------------- password database entry
X#
X# ^ ^ ^
X# | | |
X# 1 2 3
X#
X# The above reflects that a given uid can have replications between
X# YPdomain's, as well as within YPdomains.
X#
X# Level 1 (uid) is done with a dictionary.
X# Level 2 (YPdomain) is also done with a dictionary
X# Level 3 (pwent) is done with a list
X
Ximport sys
Ximport string
Ximport posix
X
Xleast_uid = 100
X
Xdef test():
X print 'hi'
X uid = {}
X domain = {}
X pwent1 = [ 'a', 'b' ]
X pwent2 = [ 'c', 'd' ]
X domain['YPee'] = pwent1
X domain['YPmae'] = pwent2
X uid[0] = domain
X print uid
X
Xdef main():
X if len(sys.argv) >= 27:
X sys.stderr.write('too many domains\n')
X sys.exit(1)
X find_username_conflicts()
X entries_at_uid = bring_in_uid_tree()
X resolve_uid_conflicts(entries_at_uid)
X output_new_database(entries_at_uid)
X
X# the sense of this is inverted in output_new_database()!
Xdef bother_with_entry(fields):
X # IE, don't do system accounts
X if string.atoi(fields[2]) >= least_uid:
X return 1
X else:
X return 0
X
Xdef find_username_conflicts():
X username_used = {}
X conflict_found = 0
X for domainname in sys.argv[1:]:
X file = open(domainname,'r')
X while 1:
X pwent = file.readline()
X if not pwent:
X break
X fields = string.splitfields(pwent,':')
X if bother_with_entry(fields):
X if username_used.has_key(fields[0]):
X conflict_found = 1
X sys.stderr.write('username conflict: '+fields[0]+'\n')
X else:
X username_used[fields[0]] = 1
X file.close()
X if conflict_found <> 0:
X sys.exit(1)
X
Xdef bring_in_uid_tree():
X entries_at_uid = {}
X prefix = chr(ord('a') - 1)
X for domainname in sys.argv[1:]:
X prefix = chr(ord(prefix) + 1)
X file = open(domainname,'r')
X domainname = prefix + ' ' + domainname
X while 1:
X pwent = file.readline()
X if not pwent:
X break
X fields = string.splitfields(pwent,':')
X if bother_with_entry(fields):
X uid = string.atoi(fields[2])
X if entries_at_uid.has_key(uid):
X # already something at this uid
X if entries_at_uid[uid].has_key(domainname):
X # already something at this uid and this domain
X entries_at_uid[uid][domainname].append(pwent)
X else:
X # already something at this uid, nothing at this domain
X entries_at_uid[uid][domainname] = [ pwent ]
X else:
X # nothing at this uid yet
X entries_at_uid[uid] = {}
X entries_at_uid[uid][domainname] = [ pwent ]
X file.close()
X return entries_at_uid
X
Xdef find_free_uid(entries_at_uid):
X possibility = least_uid
X while 1:
X if entries_at_uid.has_key(possibility):
X possibility = possibility + 1
X else:
X return possibility
X
Xdef resolve_uid_conflicts(entries_at_uid):
X for uid in entries_at_uid.keys():
X domains = entries_at_uid[uid].keys()
X domains.sort()
X if len(domains) >= 2:
X for domain in domains[1:]:
X new_uid = find_free_uid(entries_at_uid)
X entries_at_uid[new_uid] = {}
X entries_at_uid[new_uid][domain] = entries_at_uid[uid][domain]
X print 'chown',string.split(domain)[1],uid,new_uid
X
Xdef tailor_pwent(uid,domain,pwent):
X fields = string.splitfields(pwent,':')
X fields[2] = str(uid)
X file = posix.popen('./'+string.split(domain)[1]+'-paths '+fields[5],'r')
X fields[5] = file.readline()[:-1] # don't want the newline
X file.close()
X return string.joinfields(fields,':')
X
Xdef output_new_database(entries_at_uid):
X # output entries with uid < 100 in the first domain
X file = open(sys.argv[1],'r')
X while 1:
X pwent = file.readline()
X if not pwent:
X break
X fields = string.splitfields(pwent,':')
X if not bother_with_entry(fields):
X sys.stdout.write('pwent '+pwent)
X # output the shuffled stuff; uid's >= least_uid
X uids = entries_at_uid.keys()
X uids.sort()
X for uid in uids:
X domain = entries_at_uid[uid].keys()[0]
X for pwent in entries_at_uid[uid][domain]:
X new_pwent = tailor_pwent(uid,domain,pwent)
X sys.stdout.write('pwent '+new_pwent)
X
Xmain()
X
SHAR_EOF
$TOUCH -am 0724233494 uid-merge &&
chmod 0755 uid-merge ||
echo "restore of uid-merge failed"
set `wc -c uid-merge`;Wc_c=$1
if test "$Wc_c" != "4074"; then
echo original size 4074, current size $Wc_c
fi
# ============= gen-paths ==============
echo "x - extracting gen-paths (Text)"
sed 's/^X//' << 'SHAR_EOF' > gen-paths &&
X#!/bin/sh
X
Xecho 'The output of this script is not complete - expect to do some' 1>&2
Xecho 'editing!' 1>&2
X
Xecho "#!/bin/sh"
Xecho
Xecho 'echo "$@" | sed \\'
Xawk -F':' ' { if ($3 >= 100) print $6 }' | \
X sed 's#^\(.*\)/[^/]*#\1#' | \
X sort | \
X uniq | \
X sed "s/\(.*\)$/ -e 's#\1##' \\\\/"
SHAR_EOF
$TOUCH -am 0724230594 gen-paths &&
chmod 0755 gen-paths ||
echo "restore of gen-paths failed"
set `wc -c gen-paths`;Wc_c=$1
if test "$Wc_c" != "287"; then
echo original size 287, current size $Wc_c
fi
# ============= chowns ==============
echo "x - extracting chowns (Text)"
sed 's/^X//' << 'SHAR_EOF' > chowns &&
X#!/dcs/bin/python
X
Ximport sys
Ximport os
Ximport posixpath
Ximport posix
Ximport string
X
X# input is n lines for n uids to change.
X# each line has "olduid newuid" no quotes, whitespace separated
X# files are done "from here down"
X
Xdef get0():
X s = ''
X while 1:
X ch = sys.stdin.read(1)
X if not ch or ch == chr(0):
X break
X s = s + ch
X return s
X
X#def main():
X# while 1:
X# s = get0()
X# if not s:
X# break
X# print s
X
Xdef visit(newuid,top,names):
X for name in names:
X #if not os.path.isdir(name) and not os.path.islink(name):
X if not os.path.islink(name) and name <> '..':
X file = posixpath.join(top,name)
X s = posix.stat(file)
X # 4th is uid, 5th is gid, numbered starting at 0th
X if newuid.has_key(s[4]):
X print file,s[4],'->',newuid[s[4]],'\t'
X posix.chown(file,newuid[s[4]],s[5])
X else:
X print file
X
Xdef main():
X newuid = {}
X while 1:
X line = sys.stdin.readline()
X if not line:
X break
X oldnew = string.split(line)
X newuid[eval(oldnew[0])] = eval(oldnew[1])
X posixpath.walk('.',visit,newuid)
X
Xmain()
X
SHAR_EOF
$TOUCH -am 0727214494 chowns &&
chmod 0755 chowns ||
echo "restore of chowns failed"
set `wc -c chowns`;Wc_c=$1
if test "$Wc_c" != "1028"; then
echo original size 1028, current size $Wc_c
fi
exit 0