#!/usr/bin/env python

## LDAP Command Line Attribute Changer
## Originally Written by Sanrag ( sanrag[AT]students[DOT]iiit[DOT]ac[DOT]in )
## Modified by Sankalp ( sankalp_k[AT]students[DOT]iiit[DOT]ac[DOT]in )

import ldap
import sys
import md5
import string
import random
#import getpass
from optparse import OptionParser

SERVER="ldap.iiit.ac.in"
BASE_DN="ou=Users,dc=iiit,dc=ac,dc=in"
GROUPS_BASE_DN="ou=Groups,"+BASE_DN
MAIL_BASE_DN="ou=Mail,"+BASE_DN
STUDENTS_BASE_DN="ou=Students,"+BASE_DN
RESEARCH_BASE_DN="ou=Research,"+BASE_DN
ADMIN_DN="cn=admin"
MAGIC = '$1$'			# Magic string
ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

# Script Description
desc = "LDAP Attribute Changer"

# Initialize Option Parser
parser = OptionParser(description=desc)

# Add options

parser.add_option("-m","--mail",
		dest="mail",
		help="Email ID of the User",
		metavar="MAIL")

parser.add_option("-n","--newpass",
		dest="newpass",
		help="New Password for the User",
		metavar="NEWPASS")


def to64 (v, n):
    ret = ''
    while (n - 1 >= 0):
        n = n - 1
	ret = ret + ITOA64[v & 0x3f]
	v = v >> 6
    return ret


def apache_md5_crypt (pw, salt):
    # change the Magic string to match the one used by Apache
    return unix_md5_crypt(pw, salt, '$apr1$')


def unix_md5_crypt(pw, salt, magic=None):
    
    if magic==None:
        magic = MAGIC

    # Take care of the magic string if present
    if salt[:len(magic)] == magic:
        salt = salt[len(magic):]
        

    # salt can have up to 8 characters:
    salt = string.split(salt, '$', 1)[0]
    salt = salt[:8]

    ctx = pw + magic + salt

    final = md5.md5(pw + salt + pw).digest()

    for pl in range(len(pw),0,-16):
        if pl > 16:
            ctx = ctx + final[:16]
        else:
            ctx = ctx + final[:pl]


    # Now the 'weird' xform (??)

    i = len(pw)
    while i:
        if i & 1:
            ctx = ctx + chr(0)  #if ($i & 1) { $ctx->add(pack("C", 0)); }
        else:
            ctx = ctx + pw[0]
        i = i >> 1

    final = md5.md5(ctx).digest()

    for i in range(1000):
        ctx1 = ''
        if i & 1:
            ctx1 = ctx1 + pw
        else:
            ctx1 = ctx1 + final[:16]

        if i % 3:
            ctx1 = ctx1 + salt

        if i % 7:
            ctx1 = ctx1 + pw

        if i & 1:
            ctx1 = ctx1 + final[:16]
        else:
            ctx1 = ctx1 + pw
            
            
        final = md5.md5(ctx1).digest()
                                
    passwd = ''

    passwd = passwd + to64((int(ord(final[0])) << 16)
                           |(int(ord(final[6])) << 8)
                           |(int(ord(final[12]))),4)

    passwd = passwd + to64((int(ord(final[1])) << 16)
                           |(int(ord(final[7])) << 8)
                           |(int(ord(final[13]))), 4)

    passwd = passwd + to64((int(ord(final[2])) << 16)
                           |(int(ord(final[8])) << 8)
                           |(int(ord(final[14]))), 4)

    passwd = passwd + to64((int(ord(final[3])) << 16)
                           |(int(ord(final[9])) << 8)
                           |(int(ord(final[15]))), 4)

    passwd = passwd + to64((int(ord(final[4])) << 16)
                           |(int(ord(final[10])) << 8)
                           |(int(ord(final[5]))), 4)

    passwd = passwd + to64((int(ord(final[11]))), 2)


    return magic + salt + '$' + passwd



def getsalt():
	opts = "./" + string.ascii_letters + string.digits;
	return opts[random.randint(0,63)] + opts[random.randint(0,63)];

def getInfo(email):
	email = email.strip();
	userInfo = email.split("@");
	if len(userInfo) != 2:
		sys.stderr.write("Invalid e-mail format\n");
		sys.exit(1);
	return userInfo;

def change_passwd(options):
	try:
		l=ldap.initialize("ldap://"+SERVER);
	except:
		sys.stderr.write("Unable to connect LDAP Server\n");
		sys.exit(2);
	userInfo = getInfo(options.mail);
	username = userInfo[0];
	domain = userInfo[1];

       	if domain == "students.iiit.ac.in":
		searchDn = STUDENTS_BASE_DN;
	elif domain == "research.iiit.ac.in":
		searchDn = RESEARCH_BASE_DN;
	elif domain == "iiit.ac.in":
		searchDn = MAIL_BASE_DN;
	else:
		sys.stderr.write("Invalid domain. Allowed domains are\n")
		sys.stderr.write("   students.iiit.ac.in\n")
		sys.stderr.write("   research.iiit.ac.in\n")
		sys.stderr.write("   iiit.ac.in\n")
		sys.exit(3);
	searchFilter="(uid="+username+")";
	try:
		res=l.search_s(searchDn,ldap.SCOPE_SUBTREE,searchFilter,None);
		if len(res)==0:
			sys.stderr.write("User not found!\n")
			return 10;
		userdn=res[0][0]
	except:
	  	sys.stderr.write("Unable to perform search for the specified user\n")
		sys.exit(4);
	try:
		admin_password = "PUT LDAP ADMIN PASSWORD HERE";
	  	l.bind_s(ADMIN_DN,admin_password);
		newpassword = options.newpass;
	except ldap.INVALID_CREDENTIALS:
		sys.stderr.write("Incorrect Admin Password\n")
		sys.exit(5);
	else:
		new_pass = "{CRYPT}"+unix_md5_crypt(newpassword,getsalt()+getsalt()+getsalt()+getsalt());
		mod_attrs = [(ldap.MOD_REPLACE,"userPassword",new_pass)]
		res=l.modify_s(userdn,mod_attrs);
		if (res[0] != 103):
			sys.stderr.write("Unable to change password\n")
			sys.exit(6)
		sys.stderr.write("Password changed successfully\n")
		l.unbind();
		sys.exit(0);

# Parse arguments
(options,args) = parser.parse_args()

# Check for some mandatory Options
if options.mail is None:
	# No User specified
	sys.exit(1)
elif not options.newpass:
	# New Password cannot be set to NULL
	sys.exit(1)

change_passwd(options);

