#!/dcs/bin/python

# uid ----- YPdomain --- password database entry
#     \              \
#      \              -- password database entry
#       \
#        -- YPdomain --- password database entry
#         \
#          ------------- password database entry
#
# ^         ^            ^
# |         |            |
# 1         2            3
#
# The above reflects that a given uid can have replications between
# YPdomain's, as well as within YPdomains.
#
# Level 1 (uid) is done with a dictionary. 
# Level 2 (YPdomain) is also done with a dictionary
# Level 3 (pwent) is done with a list

import sys
import string
import posix

least_uid = 100

def test():
    print 'hi'
    uid = {}
    domain = {}
    pwent1 = [ 'a', 'b' ]
    pwent2 = [ 'c', 'd' ]
    domain['YPee'] = pwent1
    domain['YPmae'] = pwent2
    uid[0] = domain
    print uid

def usage():
    sys.stderr.write('To use:\n')
    sys.stderr.write('\tCreate an empty directory.\n')
    sys.stderr.write('\tIn this directory create one file per YP domain.\n')
    sys.stderr.write('\tFor each domain A, create A-paths which accepts a\n')
    sys.stderr.write('\thomedir in argv[1] and outputs a modified homedir\n')
    sys.stderr.write('\ton stdout.  Make sure the -paths scripts are 755.\n')
    sys.stderr.write('\tThen "uid-merge domaina domainb domainc"\n')
    sys.stderr.write('\tYou are expected to manually resolve username\n')
    sys.stderr.write('\tconflicts in the respective domain files.\n')
    sys.stderr.write('\tFirst column of output tags what kind of info the\n')
    sys.stderr.write('\tline holds: pwent or chown.  You can easily sed\n')
    sys.stderr.write('\tthese into a new password file and input to chowns\n')
    sys.stderr.write('\tby stripping off the first word and space.\n')
    sys.exit()

def main():
    if len(sys.argv) >= 100:
        sys.stderr.write('too many domains\n')
        sys.exit(1)
    if len(sys.argv) == 1 or (sys.argv[1:] and sys.argv[1] == '-h'):
        usage()
    find_username_conflicts()
    entries_at_uid = bring_in_uid_tree()
    resolve_uid_conflicts(entries_at_uid)
    output_new_database(entries_at_uid)

# the sense of this is inverted in output_new_database()!
def bother_with_entry(fields):
    # IE, don't do system accounts
    if string.atoi(fields[2]) >= least_uid:
        return 1
    else:
        return 0
    
def find_username_conflicts():
    username_used = {}
    conflict_found = 0
    for domainname in sys.argv[1:]:
        file = open(domainname,'r')
        while 1:
            pwent = file.readline()
            if not pwent:
                break
            fields = string.splitfields(pwent,':')
            if bother_with_entry(fields):
                if username_used.has_key(fields[0]):
                    conflict_found = 1
                    sys.stderr.write('username conflict: '+fields[0]+'\n')
                else:
                    username_used[fields[0]] = 1
        file.close()
    if conflict_found <> 0:
        sys.exit(1)

def bring_in_uid_tree():
    entries_at_uid = {}
    prefix = chr(ord('A') - 1)
    for domainname in sys.argv[1:]:
        prefix = chr(ord(prefix) + 1)
        file = open(domainname,'r')
        domainname = prefix + ' ' + domainname
        while 1:
            pwent = file.readline()
            if not pwent:
                break
            fields = string.splitfields(pwent,':')
            if bother_with_entry(fields):
                uid = string.atoi(fields[2])
                if entries_at_uid.has_key(uid):
                    # already something at this uid
                    if entries_at_uid[uid].has_key(domainname):
                        # already something at this uid and this domain
                        entries_at_uid[uid][domainname].append(pwent)
                    else:
                        # already something at this uid, nothing at this domain
                        entries_at_uid[uid][domainname] = [ pwent ]
                else:
                    # nothing at this uid yet
                    entries_at_uid[uid] = {}
                    entries_at_uid[uid][domainname] = [ pwent ]
        file.close()
    return entries_at_uid

def find_free_uid(entries_at_uid):
    possibility = least_uid
    while 1:
        if entries_at_uid.has_key(possibility):
            possibility = possibility + 1
        else:
            return possibility

def resolve_uid_conflicts(entries_at_uid):
    for old_uid in entries_at_uid.keys():
        domains = entries_at_uid[old_uid].keys()
        domains.sort()
        if len(domains) >= 2:
            for domain in domains[1:]:
                new_uid = find_free_uid(entries_at_uid)
                entries_at_uid[new_uid] = {}
                entries_at_uid[new_uid][domain] = entries_at_uid[old_uid][domain]
                del entries_at_uid[old_uid][domain]
                print 'chown',string.split(domain)[1],old_uid,new_uid

def tailor_pwent(uid,domain,pwent):
    fields = string.splitfields(pwent,':')
    fields[2] = str(uid)
    file = posix.popen('./'+string.split(domain)[1]+'-paths '+fields[5],'r')
    fields[5] = file.readline()[:-1] # don't want the newline
    file.close()
    return string.joinfields(fields,':')

def output_new_database(entries_at_uid):
    # output entries with uid < 100 in the first domain
    file = open(sys.argv[1],'r')
    while 1:
        pwent = file.readline()
        if not pwent:
            break
        fields = string.splitfields(pwent,':')
        if not bother_with_entry(fields):
            sys.stdout.write('pwent '+pwent)
    # output the shuffled stuff; uid's >= least_uid
    uids = entries_at_uid.keys()
    uids.sort()
    for uid in uids:
        domain = entries_at_uid[uid].keys()[0]
        for pwent in entries_at_uid[uid][domain]:
            new_pwent = tailor_pwent(uid,domain,pwent)
            sys.stdout.write('pwent '+new_pwent)

main()