#!/usr/bin/perl -w 
#
#@COPYING@
#

use strict;
#use utf8;
use Net::LDAP;
use Net::LDAP::Util qw(ldap_error_name ldap_error_text);
use Getopt::Long;
use Pod::Usage;
use POSIX qw(strftime);
use Encode qw(from_to encode_utf8 decode);
use Sys::Syslog;

my $HAVE_LDAPI;
my $HAVE_CRYPT_SMBHASH;
BEGIN {
	$HAVE_CRYPT_SMBHASH = 0;
	$HAVE_LDAPI = 0;
	if ( eval "require 'Crypt/SmbHash';" ) {
		$HAVE_CRYPT_SMBHASH = 1;
	}
	if ( eval "Net::LDAP->VERSION(0.28);" ) {
		$HAVE_LDAPI = 1;
	}
}

# not used yet
#if ($HAVE_CRYPT_SMBHASH) {
#	eval("use Crypt::SmbHash");
#	#ntlmgen SCALAR, LMSCALAR, NTSCALAR;
#}

sub True { return 1 };
sub False { return 0 };

my ($SMB_OC, $passdb);
our ($mesg,$entry);
my (%progs, %confs);
my ($ascii_group, $ascii_user);

################
# parse switches

my (	$opt_add,
	$opt_comment,
	$opt_delete,
	$opt_debug,
	$opt_force,
	$opt_gid,
	$opt_group,
	$opt_help,
	$opt_homedir,
	$opt_init,
	$opt_join,
	$opt_list,
	$opt_logfile,
	$opt_loginShell,
	$opt_makehomedir,
	$opt_modify,
	$opt_mode,
	$opt_ntgroup, #unused
	$opt_passwd,
	$opt_quiet,
	$opt_raw,
	$opt_remove,
	$opt_showconfig,
	$opt_skeldir,
	$opt_smbacct,
	$opt_smbconfig,
	$opt_uid,
	$opt_user,
	$opt_username,
	$opt_verbose,
	$opt_version,
	$opt_workstation,
	);

GetOptions(
	'add|a'			=> \$opt_add,
	'comment|c=s'		=> \$opt_comment,
	'config'		=> \$opt_showconfig,
	'debug=s'		=> \$opt_debug,
	'delete|d'		=> \$opt_delete,
	'force'			=> \$opt_force,
	'gid:s'			=> \$opt_gid,
	'group|g:s'		=> \$opt_group,
	'help|?|h'		=> \$opt_help, 
	'homedir=s'		=> \$opt_homedir,
	'init'			=> \$opt_init,
	'join|j'		=> \$opt_join,
	'list|l'		=> \$opt_list,
	'logfile=s'		=> \$opt_logfile,
	'makehomedir'		=> \$opt_makehomedir,
	'modify|m'		=> \$opt_modify,
	'mode=s'		=> \$opt_mode,
	'ntgroup=s'		=> \$opt_ntgroup, #unused
	'passwd|p=s'		=> \$opt_passwd,
	'quiet'			=> \$opt_quiet,
	'raw'			=> \$opt_raw,
	'remove|r'		=> \$opt_remove,
	'shell=s'		=> \$opt_loginShell,
	'skeldir|k=s'		=> \$opt_skeldir,
	'smbacct|s'		=> \$opt_smbacct,
	'smbconf=s'		=> \$opt_smbconfig,
	'uid=i'			=> \$opt_uid,
	'user|u:s'		=> \$opt_user,
	'username:s'		=> \$opt_username,
	'verbose|v'		=> \$opt_verbose,
	'version'		=> \$opt_version,
	'workstation|wks:s'	=> \$opt_workstation,
	) or pod2usage(2);



###############
# some initials

my $VERSION		=	"1.33";
my $LOGFILE_NAME	=	$opt_logfile || "/var/log/samba/log.ldapsmb";
my $DEBUGLEVEL 		= 	$opt_debug || 0 ;
my $DEBUG_PASSWORD 	= 	0;

my $LDAPSMBRC		=	$ENV{'HOME'}."/.ldapsmbrc";

# leave empty for (expensive) autodetection
my $ADMIN_DN		=	"";
my $ADMIN_PW		=	"";
my $LDAP_PORT		=	389;
my $LDAP_SERVER		=	"localhost";
my $LDAP_SSL		=	"no";
my $START_TLS		=	"";
my $SUFFIX		=	"";
my $SUFFIX_GROUPS	=	"";
my $SUFFIX_IDMAP	=	"";
my $SUFFIX_MACHINES	=	"";
my $SUFFIX_USERS	=	"";

my $HOMEDIR_ROOT	=	"/home";
my $SKELETONDIR		=	"/etc/skel";
my $UID_RANGE		=	"1000-20000";
my $GID_RANGE		=	"1000-20000";
my $PRIMARYGIDNUMBER	=	$opt_gid || 100;
my $PRIMARYGIDNUMBER_WKS= 	$opt_gid || 65534;
my $GECOS		=	"SMBLDAP-User";

# this stores maxuid and maxgid-fields in ldap
my $USE_MAXID		=	1;

# FIXME: this need to be autodetected
my $USE_LOGFILE		=	0;

# shall we log to syslog?
my $USE_SYSLOG		=	0;

# use LDAP over IPC
# this will only work with perl-ldap > 0.28
my $USE_LDAPI		=	0;
my $LDAPI_SOCKET	=	"/var/run/slapd/run/ldapi";

# if set to 1, unassigned passwords become the same as the username
my $USE_TRIVIAL_PWD	=	0;

# not yet...
my $HAVE_UTF8		=	0;


my $SMB_CONF;
my $SAMBA_SCHEMA_VERSION;

my @progs = qw( tdbdump pdbedit smbpasswd smbd testparm );
my @confs = qw( smb.conf secrets.tdb );

my $SAMBA_SMBD_VERSION = "";
my $SAMBA_22 = 1 	if ( $SAMBA_SMBD_VERSION eq "2_2" );
my $SAMBA_30 = 1	if ( $SAMBA_SMBD_VERSION eq "3_0" or "HEAD" );


# try to detect samba-version by schema-detection
sub detect_schema_from_ldap($) {

	my $hd = shift || die "no handle yet";

	# try to detect samba-version by schema-detection
	$SAMBA_SCHEMA_VERSION = get_schema_samba_ver($hd);

	$SMB_OC = "sambaSamAccount" if ($SAMBA_30);
	$SMB_OC = "sambaAccount" if ($SAMBA_22);
}

sub setup_configfile() {

	if ( $opt_mode eq "local" ) {
		$SMB_CONF = $opt_smbconfig || $SMB_CONF;
	} elsif ( $opt_mode eq "remote" ) {
		if ( ! $opt_smbconfig || ! -e $opt_smbconfig ) {
			debug(0,"running in remote-mode: need a smb.conf.\n");
			debugadd(0,"please use --smbconf=/my/path/smb.conf\n");
			return False;
		}
		$SMB_CONF = $opt_smbconfig;
	}
	return True;
}


sub find_files {

	if ( $opt_mode eq "local" ) {
		if (!find_progs(@progs)) {
			print "missing binaries\n";
			return False;
		}
		if (!find_confs(@confs)) {
			print "missing configuration\n";
			return False;
		}

		$SMB_CONF = $confs{"smb.conf"} || "";

		if (!get_smbd_samba_ver($progs{smbd})) {
			return False;
		}

		$progs{net} = find_progs("net") if ($SAMBA_30); # FIXME ??????
	}
	return True;
}


sub check_ldap_pwd {

	if ($ADMIN_PW) {
		return True;
	}

	if ($> eq "0") {
		$ADMIN_PW = find_adminpwd_in_tdb();
	}
	if ($ADMIN_PW eq "")  {
		debug(0,"sorry. you're not root. please set up your password in $LDAPSMBRC or directly in $0\n");
		return False;
	}
	return True;
}

##################
# execute switches
##################

sub check_syntax() {

	# check syntax
	if ( $opt_add ) {
		if ( !$opt_user && !$opt_group && !$opt_workstation ) {
			debug(0,"error: what do you want to add?\n");
			debugadd(0,"choose between: user, machine or group. exiting.\n");
			return False;
		}
	}

	if ( $opt_modify ) {
		if ( !$opt_user && !$opt_group && !$opt_workstation ) {
			debug(0,"error: what do you want to modify?\n");
			debugadd(0,"choose between: user, machine or group. exiting.\n");
			return False;
		}
	}

	if ( $opt_delete ) {
#	if ( defined($opt_delete) && ($opt_delete eq "") ) {
		if ( !$opt_user && !$opt_group && !$opt_workstation ) {
			debug(0,"error: what do you want to delete?\n");
			debugadd(0,"choose between: user, machine or group. exiting.\n");
			return False;
		}
	}

	if ( $opt_list ) {
		if ( !defined($opt_user) && !defined($opt_group) && !defined($opt_workstation) ) {
			debug(0,"error: what do you want to list?\n");
			debugadd(0,"choose between: user, machine or group. exiting.\n");
			return False;
		}
	}

	if ( $opt_join ) {
		if ( !$opt_user && !$opt_group ) {
			debug(0,"error: you only can join a user to a group. exiting.\n");
			return False;
		}
	}

	if ( $opt_remove ) {
		if ( !$opt_user && !$opt_group ) {
			debug(0,"error: you only can remove a user from a group. exiting.\n");
			return False;
		}
	}

        if ( !defined($opt_add) && !defined($opt_modify) && !defined($opt_delete) &&
	     !defined($opt_list) && !defined($opt_remove) && !defined($opt_join) ) {
		pod2usage(1);
		return False;
        }
	return True;
}


sub init() {

	init_logging();

	if (!detect_mode($opt_mode)) {
		return False;
	}

	if (!setup_configfile()) {
		return False;
	}

	if (!find_files()) {
		return False;
	}

	if (!parse_smbconf()) {
		return False;
	}

	if (!check_ldap_pwd()) {
		return False;
	}

	return True;
}


sub main {

	# show version
	if ( $opt_version ) {
		print "ldapsmb Version $VERSION\n";
		exit 0;
	}

	# show help
	if ( $opt_help ) {
		pod2usage(1);
		exit 0;
	}
	
	# show config
	if ( $opt_showconfig ) {
		if (!init()) {
			exit 1;
		}
		my $ldap = ldap_anon_bind();
		detect_schema_from_ldap($ldap);
		print_smb_conf($ldap);
		ldap_unbind($ldap);
		exit 0;
	}

	# init
	if ( $opt_init ) {
		if (!init()) {
			exit 1;
		}
		my $ldap = ldap_bind();
		ldap_init($ldap);
		ldap_unbind($ldap);
		exit 0;
	}

	if (!check_syntax()) {
		exit 1;
	}

	#################################
	#### from here on we do something
	#################################

	if (!init()) {
		exit 1;
	}

	my $ldap = ldap_bind();

	detect_schema_from_ldap($ldap);

	if (!prepare_account($ldap)) {
		goto failed;
	}

	# add user
	if ( $opt_add && $opt_user ) {
		my $USER_FILTER = sprintf "(&(|(objectclass=%s)(objectclass=%s))(uid=%s))",
			"posixAccount", $SMB_OC, $opt_user;
		if ( ldap_search($ldap, $SUFFIX_USERS, $USER_FILTER) ) {
			if ( $opt_smbacct ) {
				debug(0,"adding sambaaccount to existing user: [$opt_user]\n");
				if (!ldap_smbuser_add($opt_user)) {
					goto failed;
				}
				goto done;
			}
			debug(0,"user [$opt_user] already exists. exiting\n");
			goto failed;
		} else {
			if (!$opt_uid) { 
				$opt_uid = ldap_uid($ldap); 
			}
			if ( !$opt_uid ) {
				debug(0,"please define uidNumber with --uid=uidNumber\n");
				goto failed;
			}
			debug(0,"adding user: [$opt_user] (with uidNumber: $opt_uid)\n");
			if (!ldap_posixuser_add($ldap, $opt_user, $opt_uid)) {
				goto failed;
			}
			if ( $opt_smbacct ) {
				debug(0,"adding sambaaccount: [$opt_user]\n");
				if (!ldap_smbuser_add($opt_user)) {
					goto failed;
				}
			}
		}
	};


	# add workstation
	if ( $opt_add && $opt_workstation ) {
		my $USER_FILTER="(&(objectclass=posixAccount)(uid=$opt_workstation))";
		if ( ldap_search($ldap, $SUFFIX_MACHINES, $USER_FILTER) ) {
			if ( $opt_smbacct ) {
				debug(3,"adding machine-account: [$opt_workstation]\n");
				if (!ldap_smbwks_add($opt_workstation)) {
					goto failed;
				}
				goto done;
			}
			debug(0,"machine [$opt_workstation] already exists. exiting.\n");
			goto failed;
		} else {
			if (!$opt_uid) { 
				$opt_uid = ldap_uid($ldap);
			} 
			if ( !$opt_uid ) {
				debug(0,"please define uidNumber with --uid=uidNumber\n");
				goto failed;
			}
			debug(0,"adding machine: [$opt_workstation] (with uidNumber: $opt_uid)\n");
			if (!ldap_posixwks_add($ldap, $opt_workstation, $opt_uid)) {
				goto failed;
			}
			if ( $opt_smbacct ) {
				debug(0,"adding machine-account: [$opt_workstation]\n");
				if (!ldap_smbwks_add($opt_workstation)) {
					goto failed;
				}
			} 
		}
	};

	# modify user or workstation ("set primary group script" makes no difference)
	if ( $opt_modify && $opt_user ) {

		my $USER_FILTER="(&(objectclass=posixAccount)(uid=$opt_user))";
		my $num_result = ldap_search($ldap, $SUFFIX_USERS, $USER_FILTER);
		if ($num_result == 1) {
			debug(0,"modifying user: [$opt_user]\n");
			if (!ldap_usermodify($ldap, $opt_user)) {
				goto failed;
			}
			goto done;
		}
		$num_result = ldap_search($ldap, $SUFFIX_MACHINES, $USER_FILTER);
		if ($num_result == 1) {
			debug(0,"modifying workstation: [$opt_user]\n");
			if (!ldap_usermodify($ldap, $opt_user)) {
				goto failed;
			}
			goto done;
		} 
		debug(0,"no such account [$opt_user] to modify\n");
		goto failed;
	};


	# list user
	if ( $opt_list && defined($opt_user) ) {

		my $no_user = $opt_user;
		$opt_user = $opt_user || "*";
		my $USER_FILTER = sprintf("(&(objectclass=%s)(uid=%s))", "posixAccount", $opt_user);
		
		if ( ldap_search($ldap, $SUFFIX_USERS, $USER_FILTER) ) {
			debug(5,"list user: [$opt_user]\n");
			if (defined($opt_smbacct)) {
				if (!ldap_list_smbacct($ldap, $opt_user, "user")) {
					goto failed;
				}
			} else {
				if (!ldap_list_user($ldap, $opt_user, "user")) {
					goto failed;
				}
			}
		} elsif ( $no_user eq "") {
			debug(0,"no users found with samba account found in database\n");
			goto done;
		} else {
			debug(0,"no user [$opt_user] with samba account in database\n");
			goto failed;
		}
	};


	# list workstations
	if ( $opt_list && defined($opt_workstation) ) {

		my $no_workstation = $opt_workstation;
		$opt_workstation = $opt_workstation || "*";
		my $USER_FILTER = sprintf("(&(objectclass=%s)(uid=%s))", $SMB_OC, $opt_workstation);
		
		if ( ldap_search($ldap, $SUFFIX_MACHINES, $USER_FILTER) ) {
			debug(5,"list samba workstations: [$opt_workstation]\n");
			if (!ldap_list_smbacct($ldap, $opt_workstation,"workstation")) {
				goto failed;
			}
		} elsif ( $no_workstation eq "") { 
			debug(0,"no workstations found in database\n");
			goto done;
		} else {
			debug(0,"no workstation [$opt_workstation] with samba account in database\n");
			goto failed;
		}
	};


	# list groups 
	if ( $opt_list && defined($opt_group) ) {

		my $nogroup = $opt_group;
		$opt_group = $opt_group || "*";
		my $GROUP_FILTER = sprintf("(&(objectclass=%s)(cn=%s))", "posixGroup", $opt_group);

		if ( ldap_search($ldap, $SUFFIX_GROUPS, $GROUP_FILTER) ) {
			debug(5,"list members of group: [$opt_group]\n");
			ldap_list_group($ldap, $opt_group );
		} elsif ( $nogroup eq "") {
			debug(0,"no groups found in database\n");
			goto done;
		} else {
			debug(0,"no group [$opt_group] in database\n");
			goto failed;
		}
	}


	# delete user or workstation
	if ( $opt_delete && ($opt_user||$opt_workstation) ) {
#	if ( defined($opt_delete) && $opt_delete eq "" && ($opt_user||$opt_workstation) ) {

		my $name   = $opt_user || $opt_workstation;
		my $type   = sprintf("%s", $opt_user ? "user" : "workstation");
		my $suffix = sprintf("%s", $opt_user ? $SUFFIX_USERS : $SUFFIX_MACHINES);

		my $filter = sprintf "(&(|(objectclass=%s)(objectclass=%s))(uid=%s))", 
			"posixAccount", $SMB_OC, $name;

		my $num_result = ldap_search($ldap, $suffix, $filter);

		if ($num_result == 0) {
			debug(0,"no such $type [$name] to delete\n");
			goto failed;
		}

		if ($num_result > 1) {
			debug(0,("more then one to delete. exiting\n"));
			goto failed;
		}

		debug(0,"deleting $type: [$name]\n");
		if (!ldap_delete_internal($ldap, $name, $type, $entry->dn)) {
			goto failed;
		}
		goto done;
	};

	# add group
	if ( $opt_add && $opt_group ) {
		my $GROUP_FILTER = sprintf("(&(objectclass=%s)(cn=%s))", "posixGroup", $opt_group);
		my $num_result = ldap_search($ldap, $SUFFIX_GROUPS, $GROUP_FILTER);
		if ($num_result == 1) {
			my $gidnumber = $entry->get_value( 'gidNumber');
			if ( $opt_smbacct and $SAMBA_30 ) {
				debug(0,"adding ntgroup-mapping for unix-group: [$opt_group]\n");
				ldap_ntgroup_add($opt_group, $gidnumber);
				goto done;
			}
			debug(0,"group [$opt_group] already exists. exiting.\n");
			goto failed;
		} 
		if ($num_result == 0) {
			if (!$opt_gid) { 
				$opt_gid = ldap_gid($ldap);
			} 
			if ( !$opt_gid ) {
				debug(0,"please define gidNumber with --gid=gidNumber\n");
				goto failed;
			} 
			debug(0,"adding group: [$opt_group] (with gidNumber: $opt_gid)\n");
			if (!ldap_posixgroup_add($ldap, $opt_group, $opt_gid)) {
				goto failed;
			}
			if ( $opt_smbacct and $SAMBA_30 ) {
				debug(0,"adding ntgroup: [$opt_group]\n");
				ldap_ntgroup_add($opt_group);
			}
		}
	};


	# delete groupmapping
	if ( $opt_delete && $opt_group && $opt_smbacct && $SAMBA_30 ) {
		ldap_ntgroup_delete($opt_group);
	}


	# delete group
	if ( $opt_delete && $opt_group && !$opt_smbacct ) {
		my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$opt_group))";
		if ( ldap_search($ldap, $SUFFIX_GROUPS, $GROUP_FILTER) ) {
			debug(0,"deleting group: [$opt_group]\n");
			if (!ldap_delete_internal($ldap, $opt_group, "group", $entry->dn)) {
				goto failed;
			}
			goto done;
		} else {
			debug(0,"no such group [$opt_group] to delete. exiting.\n");
			goto failed;
		}
	};


	# add user to group
	if ( $opt_join && $opt_user && $opt_group ) {
		if (!join_or_remove_requisites($ldap, $opt_user, $opt_group)) {
			goto failed;
		}
		debug(0,"adding user [$opt_user] to group [$opt_group]\n");
		if (!ldap_user_group_member($ldap, $opt_user, $opt_group, "join")) {
			goto failed;
		}
	};


	# remove user from group
	if ( $opt_remove && $opt_user && $opt_group ) {
		if (!join_or_remove_requisites($ldap, $opt_user, $opt_group)) {
			goto failed;
		}
		debug(0,"removing user [$opt_user] from group [$opt_group]\n");
		if (!ldap_user_group_member($ldap, $opt_user, $opt_group, "remove")) {
			goto failed;
		}
	}


 done:
	ldap_unbind($ldap);
	close_logging();
	exit 0;

 failed:
	ldap_unbind($ldap);
	close_logging();
	exit -1;
}

main();

###############
# subroutines #
###############

sub get_rdn ($) {

	my $dn = shift;

	# escape "\,"
	# ads is escaping "," with a backslash (not RFC-conform), openldap converts "," into ascii "\2C")
	$dn =~ s/\\,/sumitstring/g;
	my ($rdn,@tmp) = split(/,/,$dn) ;
	$rdn =~ s/sumitstring/\\,/g;

	return $rdn;
}


sub naming_attribute_needs_update ($@) {

	my $dn = shift;
	my @changes = shift;

	my $rdn = get_rdn($dn);
	my $naming_attr = $rdn;
	$naming_attr =~ s/=.*//;

	foreach my $entry (@changes) {
		next if (!$entry);
		debug(10,"parsing entry: $entry\n");
		if ($naming_attr =~ /^$entry$/i) {
			debug(10,"found naming attribute to change: $entry\n");
			return $entry;
		}
	}
	
	return undef;
}

sub ldap_modrdn ($$$) {

	my $hd = shift;
	my $dn = shift;
	my $newrdn = shift;

	$newrdn = sprintf("%s=%s", "uid", $newrdn);

	my $tmp = get_rdn($dn);
	if ($tmp =~ /^$newrdn$/i) {
		debug(10,"FIXME: nothing to do, no change on the rdn-attribute\n");
		return True;
	}

	$mesg = $hd->moddn ( $dn, deleteoldrn => 1, newrdn => $newrdn);

	# was there an error?
	if ($mesg->code) {
		ldap_status("modrdn", $mesg, 3);
		return False;
	} 
	debug(5,"modified rdn of dn [$dn].\n");
	return True;
}


sub join_or_remove_requisites ($$$) {

	my $hd = shift;
	my $user = shift;
	my $group = shift;

	my $user_filter = sprintf("(&(objectclass=%s)(uid=%s))", "posixAccount", $user); 
	my $group_filter = sprintf("(&(objectclass=%s)(cn=%s))", "posixGroup", $group);

	# check if user is there
	if ( ldap_search($hd, $SUFFIX_USERS, $user_filter) ) {
		debug(5,"ok. user [$user] exists in ldap.\n");
	} elsif ( defined(getpwnam($user)) ) {
		debug(5,"ok. user [$user] exists somewhere else in the name service switch.\n");
	} else {
		debug(0,"no such user [$user]. exiting.\n");
		return False;
	}

	# check if group is there
	my $num_group = ldap_search($hd, $SUFFIX_GROUPS, $group_filter);
	if ($num_group > 1) {
		debug(0,"group [$group] exists more then once.\n");
		return False;
	}

	if ($num_group < 1) {
		debug(0,"no such group [$group]. exiting.\n");
		return False;
	}
	
	debug(5,"ok. group [$group] exists\n");
	return True;
}

sub ldap_list_smbacct {

	my $hd = shift;
	my $acct = shift || "*";
	my $type = shift;

	my $base;
	if ( $type =~ /user/i ) {
		$base = $SUFFIX_USERS;
	} elsif ( $type =~ /workstation|machine/ ) {
		$base = $SUFFIX_MACHINES;
	}
	
	$mesg = $hd->search(
		base => "$base", 
		filter => "(&(objectclass=$SMB_OC)(uid=$acct))");
	
	# was there an error?
	if ($mesg->code) {
		ldap_status("listing samba account", $mesg, 3);
		return False;
	}

	foreach my $entry ($mesg->all_entries) {

		my $uid 		= $entry->get_value( 'uid' );
		my $cn 			= $entry->get_value( 'cn' ) || "";
		my $uidNumber 		= $entry->get_value( 'uidNumber' ) || "";

		if ( $opt_raw ) {

			$entry->dump;
			next;

		} elsif ( $opt_verbose ) {

			my $acct		= $entry->get_value( 'sambaAcctFlags' ) || 
						  $entry->get_value( 'acctFlags' ) || "";
			my $description		= $entry->get_value( 'description' ) || "";
			my $displayName		= $entry->get_value( 'displayName' ) || "";
			my $domain		= $entry->get_value( 'sambaDomain' ) || 
						  $entry->get_value( 'domain' ) || "";
			my $gidNumber 		= $entry->get_value( 'gidNumber' ) || "";
			my $homeDirectory	= $entry->get_value( 'sambaHomeDirectory' ) || 
						  $entry->get_value( 'homeDirectory' );
			my $homeDrive 		= $entry->get_value( 'sambaHomeDrive' ) || 
						  $entry->get_value( 'homeDrive') || "";
			my $primaryGroupID	= $entry->get_value( 'primaryGroupID' );
			my $primaryGroupSID	= $entry->get_value( 'sambaPrimaryGroupSID' );
			my $profilePath		= $entry->get_value( 'sambaProfilePath' ) || 
						  $entry->get_value( 'profilePath' ) || "";
			my $rid 		= $entry->get_value( 'rid' );
			my $scriptPath 		= $entry->get_value( 'sambaScriptPath' ) || 
						  $entry->get_value( 'scriptPath' ) || "";
			my $sid 		= $entry->get_value( 'sambaSID' );
			my $wks			= $entry->get_value( 'sambaUserWorkstations' ) || 
						  $entry->get_value( 'userworkstations' ) || "";
			from_to($description, "utf-8", "iso-8859-1" );
	
			print "Unix username:\t\t$uid\n";
			print "NT username:\t\t$displayName\n";
			print "Account Flags:\t\t$acct\n";
			print "user ID/Group:\t\t$uidNumber / $gidNumber\n";
			if ( $SAMBA_22 ) { 
				print "user RID:\t$rid\n";
				print "user GRID:\t$primaryGroupID\n";
				my $ret = check_rid_allocation($uidNumber,$gidNumber,$rid,$primaryGroupID); 
				if ($ret) {
					debug(0,"$ret\n");
				}
			} elsif ( $SAMBA_30 ) {
				print "User SID:\t\t$sid\n";
				print "User Primary Group SID:\t$primaryGroupSID\n";
			}
			print "Full Name:\t\t$cn\n";
			print "Home Directory:\t\t$homeDirectory\n" if ($homeDirectory);
			print "HomeDir Drive:\t\t$homeDrive\n";
			print "Logon Script:\t\t$scriptPath\n";
			print "Profile Path:\t\t$profilePath\n";
			print "Domain:\t\t$domain\n" if ($domain);
			print "Workstations:\t\t$wks\n" if ($wks);
			print "Account Desc.:\t\t$description\n" if ($description);
			print "\n";
			print "------------------------------------\n" if ($mesg->entries > 1);
	
		} else {
	
			print "$uid:$uidNumber:$cn\n";
	
		}
	}
	return True;
}


sub ldap_list_group {

	my $hd = shift;
	my $group = shift || "*";

	$mesg = $hd->search (
		base => "$SUFFIX_GROUPS", 
		filter => "(&(objectclass=posixGroup)(cn=$group))"
	);

	# was there an error?
	if ($mesg->code) {
		ldap_status("listing group members", $mesg, 3);
	}

	foreach my $entry ($mesg->all_entries) {
	
		my $cn 		= $entry->get_value( 'cn' );
		my $gid 	= $entry->get_value( 'gidNumber' );
		my @memberUid 	= $entry->get_value( 'memberUid' );
		my $description = $entry->get_value( 'description' ) || "";
		my $sid 	= $entry->get_value( 'sambaSid' ) || "";
		my $nt_name 	= $entry->get_value( 'displayName' ) || "";
		my $group_type 	= $entry->get_value( 'sambaGroupType' ) || "";
		if (! $HAVE_UTF8) {
			from_to($cn, "utf-8", "iso-8859-1" );
			from_to($nt_name, "utf-8", "iso-8859-1" );
			from_to(@memberUid, "utf-8", "iso-8859-1" );
			from_to($description, "utf-8", "iso-8859-1" );
		}
		if ( $opt_verbose ) {
			print "Groupname:\t$cn\n";
			print "NT-Groupname:\t$nt_name\n" if ($nt_name);
			print "Group ID:\t$gid\n";
			print "Group SID:\t$sid\n" if ($sid);
			print "Group Type:\t$group_type\n" if ($group_type);	#FIXME
			printf "Groupmembers:\t%s\n", join(",",@memberUid) if (@memberUid);
			print "Description:\t$description\n" if ($description);
			print "\n";
			print "--------------------------------\n" if ($mesg->entries > 1);
		} elsif ( $opt_raw ) {
			$entry->dump;
		} else {
			printf "$cn:x:$gid:%s\n", join(",",@memberUid);
		}
	}
}


sub ldap_list_user ($$) {

	my $hd = shift;
	my $user = shift || "*";
	
	$mesg = $hd->search (
		base => "$SUFFIX_USERS", 
		filter => "(&(objectclass=posixAccount)(cn=$user))"
	);

	# was there an error?
	if ($mesg->code) {
		ldap_status("listing users", $mesg, 3);
		return False;
	} 

	foreach my $entry ($mesg->all_entries) {

		my $cn 		= $entry->get_value( 'cn' );
		my $uid 	= $entry->get_value( 'uidNumber' );
		my $gid 	= $entry->get_value( 'gidNumber' );
		my $home 	= $entry->get_value( 'homeDirectory' );
		my $shell 	= $entry->get_value( 'loginShell' );
		my $description = $entry->get_value( 'description' ) || "";
		if (! $HAVE_UTF8) {
			from_to($cn, "utf-8", "iso-8859-1" );
			from_to($description, "utf-8", "iso-8859-1" );
		}
		if ( $opt_verbose ) {
			print "Username:\t$cn\n";
			print "User ID:\t$uid\n";
			print "Description:\t$description\n" if ($description);
			print "Group ID:\t$gid\n";
			print "Home Directory: $home\n";
			print "Shell:\t\t$shell\n";
			print "\n";
			print "--------------------------------\n" if ($mesg->entries > 1);
		} elsif ( $opt_raw ) {
			$entry->dump;
		} else {
			print "$cn:x:$uid:\n";
		}
	}
	return True;
}


sub ldap_posixuser_add ($$$) {

	my $hd = shift;
	my $uid = shift;
	my $uidNumber = shift;

	my $userPassword = undef;

	my $home	= $opt_homedir 	|| "$HOMEDIR_ROOT/$ascii_user";
	my $shell	= $opt_loginShell || "/bin/bash";
	my $cn 		= $opt_comment	|| $uid;

	# check passwd
	my $salt =  pack("C2",(int(rand 26)+65),(int(rand 26)+65));
	if ( $USE_TRIVIAL_PWD ) {
		$opt_passwd = $opt_user;
		$userPassword =	crypt "$opt_passwd",$salt;
		$userPassword =	"{crypt}".$userPassword;
	} elsif ( defined($opt_passwd) ) {
		$userPassword =	crypt "$opt_passwd",$salt;
		$userPassword =	"{crypt}".$userPassword;
	} else {
		$userPassword = "";
	}

	# some more defaults
	my $gidNumber	=	$PRIMARYGIDNUMBER;
	my $dn		=	sprintf("uid=%s,%s", $uid, $SUFFIX_USERS);

	# create a new object
	$entry = Net::LDAP::Entry->new;

	$entry->dn ( "$dn" );
	$entry->add ( objectclass => [ 'account','posixAccount' ] );
	$entry->add (	'cn'            => $cn,
			'uid'           => $uid,
			'uidNumber'     => $uidNumber,
			'gidNumber'     => $gidNumber,
			'homeDirectory' => $home,
			'loginShell'    => $shell,
#			'gecos'         => $GECOS, 
	);
	$entry->add (	'userPassword'  => "$userPassword" ) if ( $userPassword );

	if ( ldap_update($hd, $entry, "critical", "adding user") ) {
		debug(5,"added user [$uid] posixAccount.\n");
	} else {
		return False;
	}

	# create home directory and copy skeleton files
	if ( $opt_makehomedir ) {
		if (! -d "$home") {
			debug( 5, "Creating home directory <".$home."> of user [$opt_user]\n");
			mkdir( "$home") or die "Can't create directory: $!\n";
			chown( "$uidNumber", "$gidNumber", "$home");
		}
		$opt_skeldir = $opt_skeldir || $SKELETONDIR;
		debug( 5, "Copying skeleton files from <".$opt_skeldir."> to <".$home.">\n");
		system( "/usr/bin/find \"$opt_skeldir\" -mindepth 1 -maxdepth 1 -print0 | /usr/bin/xargs -0 /bin/cp -a --target-directory=\"$home\"");
		debug( 5, "Change owner in <".$home."> to <".$uidNumber.">:<".$gidNumber.">\n");
		system( "/usr/bin/find \"$home\" -mindepth 1 -maxdepth 1 -print0 | /usr/bin/xargs -0 /bin/chown \"$uidNumber\":\"$gidNumber\"");
	}
	return True;
}


sub ldap_smbuser_add ($) {

	my $uid = shift;	

	# create sambaAccount of user
	if ( $opt_smbacct && ($opt_mode eq "local") ) {

		$opt_passwd = $opt_passwd || "";
		debug(5,"Creating samba account of user [$uid]\n");
		if ($DEBUG_PASSWORD) {
			debugadd(5,"with password <$opt_passwd>.\n");
		} else {
			debugadd(5,"with password xxx (not logged).\n");
		}
	
		# although displayName is a UTF-8 string (1.3.6.1.4.1.1466.115.121.1.15) 
		# pdbedit is not encoding it correctly. FIXME: tell samba-team 
		my $gecos = (getpwnam($uid))[6];
		my $ascii_displayName = umlaut2ascii($gecos);
	
		if ( $SAMBA_22 ) {
			my $pdbedit_opts = "-a -u -b -f '$ascii_displayName'";
			system( "/bin/echo -e \"$opt_passwd\\n$opt_passwd\" | $progs{pdbedit} $pdbedit_opts \"$uid\"");
		
		} elsif ( $SAMBA_30 ) {
			# FIXME: patch pdbedit!
#			my $pdbedit_opts = "-a -u -b ldapsam://$LDAP_SERVER:$LDAP_PORT";
#			my $smbpasswd_opts = "-a -e -s";
#			system( "/bin/echo -e \"$opt_passwd\\n$opt_passwd\" | $progs{smbpasswd} $smbpasswd_opts \"$uid\"");
			my $pdbedit_opts = "-a -f '$ascii_displayName'";
			system( "$progs{pdbedit} -d 0 $pdbedit_opts \"$uid\" 2>&1 > /dev/null");
		}
		# inform if there was an rc != 0
		if ($?) {
			debug(0,"Creating samba account of user [$uid] failed.\n");
			return False;
		}
	}
	return True;
}


sub ldap_usermodify ($$) {

	my $hd = shift;
	my $user = shift;

	my ($homeDirectory,$userPassword,$cn);
	my %changes = ();
	my %sambachanges = ();

	if ( $opt_homedir ) {
		$changes{'homeDirectory'} = $opt_homedir;
		$sambachanges{'-h'} = "";
	}
	if ( $opt_gid ) {
		$changes{'gidNumber'} = $opt_gid;
		$sambachanges{'-g'} = "";
	}
	if ( $opt_uid ) {
		$changes{'uidNumber'} = $opt_uid;
		$sambachanges{'-u'} = "";
	}
	if ( $opt_username ) {
		$changes{'uid'} = $opt_username;
	}
	if ( $opt_loginShell ) {
		$changes{'loginShell'} = $opt_loginShell;
	}
	if ( $opt_passwd ) {
		my $salt =  pack("C2",(int(rand 26)+65),(int(rand 26)+65));
		$changes{'userPassword'} = crypt "$opt_passwd",$salt;
		$changes{'userPassword'} = "{crypt}".$changes{'userPassword'};
	}
	if ( $opt_comment ) {
		$changes{'cn'} = $opt_comment;
		$sambachanges{'-f'} = "\"$opt_comment\"";
	}
	my $dn = $entry->dn;

	my $attr = naming_attribute_needs_update($dn, %changes);
	if ($attr) {
		debug(3,"have to change naming attribute for [$dn]\n");
		if (!ldap_modrdn($hd, $dn, $opt_username)) {
			return False;
		}
		my $num_result = ldap_search($hd, $SUFFIX_USERS, "(&(objectclass=posixAccount)(uid=$opt_username))");
		if ($num_result != 1) {
			debug(0,("could not find renamed account. DIT corrupt?\n"));
			return False;
		} else {
			# reset $dn
			$dn = $entry->dn;
		}
	}

	$mesg = $hd->modify( "$dn", replace => \%changes);
	# was there an error?
	if ($mesg->code) {
		ldap_status("modifying user", $mesg, 3);
	} else {
		debug(5,"modified posixAccount of user [$opt_user].\n");
	}

	# apply sambaAccount changes
	if ( $opt_smbacct && ($opt_mode eq "local") ) {
		debug( 5,"modifying samba account of user [$opt_user].\n");
		if ( $opt_passwd ) {
			if ($DEBUG_PASSWORD) {
				debugadd(5,"with password [opt_passwd].\n");
			}
			system( "/bin/echo -e \"$opt_passwd\\n$opt_passwd\" | $progs{smbpasswd} -s $opt_user --debug 0 2>&1 >/dev/null");
		}
		my ($opt,$arg);
		my $pdbeditopts = "-u \"$opt_user\"";
		while (($opt, $arg) = each %sambachanges) {
			debug(3,"$opt, $arg");
			$pdbeditopts .= " ".$opt." ".$arg;
		}
		debug(3,"$progs{pdbedit} $pdbeditopts");
		system( "$progs{pdbedit} $pdbeditopts -d0 2>&1 >/dev/null");
		# inform if there was an rc != 0
		if ($?) {
			debug(0,"modifying samba account of user [$opt_user] failed.\n");
		}
	}

	return True;
}


sub ldap_posixwks_add ($$) {

	my $hd = shift;
	my $uid = shift;
	my $uidNumber = shift;

	my $home		= $opt_homedir || "/dev/null";
	my $shell		= $opt_loginShell || "/bin/false";
	my $gidNumber		= $PRIMARYGIDNUMBER_WKS;
	my $dn			= sprintf("uid=%s,%s", $uid, $SUFFIX_MACHINES);
	my $cn 			= $opt_comment || "Windows Workstation ".uc($uid);

	# create a new object
	$entry = Net::LDAP::Entry->new;

	$entry->dn ( "$dn" );
	$entry->add ( objectclass => [ 'account','posixAccount' ] );
	$entry->add (	'cn'            => $cn,
			'uid'           => $uid,
			'uidNumber'     => $uidNumber,
			'gidNumber'     => $gidNumber,
			'homeDirectory' => $home,
			'loginShell'    => $shell,
#			'gecos'         => $GECOS,
	);

	# update
	if (!ldap_update($hd, $entry, "critical", "adding machine account") ) {
		return False;
	}

	debug(5,"added machine account [$uid]\n");
	return True;
}


sub ldap_smbwks_add {

	my $wks = shift;

	# create sambaAccount of machine
	if ( $opt_smbacct && ($opt_mode eq "local") ) {
		debug(10,"Creating samba account of machine [$wks].\n");
		if (! system( "$progs{smbpasswd} -a -m '$wks'") ) {
			debug(0,"Creating samba account of machine [$wks] failed.\n");
			return False;
		}
	}
	return True;
}

sub ldap_delete_internal ($$$$) {

	my $hd = shift;
	my $name = shift;
	my $type = shift;
	my $dn = shift;
	
	if ($type !~ /user|group|workstation/ ) {
		debug(0,"unkown type ($type). cannot delete\n");
		return False;
	}

	my $mesg = $hd->delete( $dn );
	# was there an error?
	if ($mesg->code) {
		ldap_status("deleting $type", $mesg, 3);
	} else {
		debug(3,"deleted $type $name ($dn)\n");
	}
	return True;
}

sub ldap_posixgroup_add ($$) {

	my $hd 		= shift;
	my $group	= shift;
	my $gidNumber 	= shift;

	my $dn 		= sprintf("cn=%s,%s", $group, $SUFFIX_GROUPS);

	# create a new object
	$entry = Net::LDAP::Entry->new;

	$entry->dn ( "$dn" );
	$entry->add ( objectclass => [ 'posixGroup', 'top', 'namedObject' ] );
	$entry->add (	'cn'            => "$group",
			'gidNumber'     => "$gidNumber");

	# update
	if (!ldap_update($hd, $entry, "critical", "adding posixgroup")) {
		return False;
	}
	debug(10,"added posix group [$group] (with gidNumber: $gidNumber)\n");
	return True;
}


sub ldap_ntgroup_add {

	my $ntgroup = shift;
	my $rid = shift; # || "$gidNumber"; !!!!!!!!!!!!!!
	my $comment = "No comment";
	my $ntgroup_type = "domain";
	my $ret = system("$progs{net} groupmap add \
		rid=$rid \
		unixgroup=$ntgroup \
		type=$ntgroup_type \
		ntgroup=$ntgroup \
		comment='$comment' \
		-d 0 2>&1 >/dev/null");
	return $ret;
}


sub ldap_ntgroup_delete {

	my $ntgroup = shift;
	my $ret = system("$progs{net} groupmap delete ntgroup=$ntgroup \
		-d 0 2>&1 >/dev/null");
	return $ret;
}


sub ldap_user_group_member ($$$$) {

	my $hd = shift;
	my $user = shift;
	my $group = shift;
	my $mode = shift;

	my $user_is_member = False;
	my @members;
	my $GROUP_FILTER = "(&(objectclass=posixGroup)(cn=$group))";
	my $mesg = ldap_search($hd, "$SUFFIX_GROUPS", "$GROUP_FILTER");
	my $mem_ref = $entry->get_value( 'memberUid', asref => 1 );
	if ($mem_ref) {
		@members = @$mem_ref;
		debug(5,"group [$group] currently has these members: [", join(", ", @members), "]\n");
		foreach my $member (@members) {
			if ($member eq $user ) { $user_is_member = True; }
		}
	}

	if ($mode eq "join") {
	
		if ($user_is_member) {
			debug(0,"doing nothing. [$user] is already a member of [$group]\n");
			return True;
		}
	
		push (@members,$user);
		$entry->add( 'memberUid' => "$user");
	}

	if ($mode eq "remove") {
	
		if (!$user_is_member) {
			debug(0,"doing nothing. [$user] is not a member of [$group]\n");
			return True;
		}

		$entry->delete( 'memberUid' => ["$user"] );
	}
	
	debug(5,"group [$group] now has these members: [", join(", ", @members), "]\n");

	# update
	if (!ldap_update($hd, $entry, "critical", "ldap_user_group_member modify") ) {
		return False;
	}
	
	if ($mode eq "join") {
		debug(10,"added user [$user] to posix group [$group]\n");
	} elsif ($mode eq "remove") {
		debug(10,"removed user [$user] from posix group [$group]\n");
	}
	
	return True;
}

#######################
# basic ldap operations
#######################

sub ldap_search {

	debug(5,"################### query LDAP ######################\n");

	my $hd		= shift;
	my $base 	= shift || $SUFFIX;
	my $filter 	= shift || "(objectclass=*)";
	my $attrs 	= shift || "*";
	my $scope 	= shift || "sub";

	# construct the query
	my $mesg = $hd->search (
		base    =>      "$base",
		filter  =>      "$filter",
		attrs   =>      [ $attrs, '+' ],
		scope   =>      "$scope"
	);

	# was there an error?
	if ($mesg->code) {
		ldap_status("searching", $mesg, 3);
		debug(3,"\tsearch with:\n");
		debugadd(3,"\t\tbase\t\"$base\"\n");
		debugadd(3,"\t\tfilter\t\"$filter\"\n");
		debugadd(3,"\t\tattrs\t\"$attrs\"\n");
		debugadd(3,"\t\tscope\t\"$scope\"\n");
		debugadd(3,"\tgave an error\n");
		die; #sure?
	} else {
		debugadd(5,"\tsearch with:\n");
		debugadd(5,"\t\tbase\t\"$base\"\n");
		debugadd(5,"\t\tfilter\t\"$filter\"\n");
		debugadd(5,"\t\tattrs\t\"$attrs\"\n");
		debugadd(5,"\t\tscope\t\"$scope\"\n");
		debugadd(5,"\tgave no error\n");
	}

	# report back
	$entry = $mesg->entry;
	if ( ! $entry ) {
		debugadd(5,"no entry found with filter \"$filter\"\n");
		debugadd(5,"################ finished LDAP query ################\n");
		return 0;
	} else {
		debugadd(5,"found ".$mesg->count." matching entrie(s)\n");
		for (my $i = 0; $i < $mesg->count; $i++) {
			debugadd(5,"found entry with dn:\t\"".$mesg->entry($i)->dn."\"\n");
		}
		debugadd(5,"################ finished LDAP query ################\n");
		return $mesg->count;
	}
}


sub ldap_bind {

	my $mode = shift || "qualified";
	my $mesg;
	my $status = "";
	my $ldap;

	if ($USE_LDAPI && $HAVE_LDAPI) {
		debug(10,"opening LDAPI socket ($LDAPI_SOCKET)\n");
		my $ldapi_url = sprintf("ldapi://%s", join("%2f", split(/\//, $LDAPI_SOCKET)));
		$ldap = Net::LDAP->new( $ldapi_url ) || 
			die "could not open LDAPI socket ($LDAPI_SOCKET): $@";
	} else {
		debug(10,"opening socket to $LDAP_SERVER:$LDAP_PORT\n");
		( $ldap = Net::LDAP->new( $LDAP_SERVER, port => $LDAP_PORT, version => 3 ) ) || 
		( $ldap = Net::LDAP->new( $LDAP_SERVER, port => $LDAP_PORT, version => 2 ) ) || 
			die "could not open TCP/IP socket: $@";
	}

	if ($START_TLS) {
		$mesg = $ldap->start_tls( verify => 'none' );
		if ($mesg->code) {
			ldap_status("starting tls", $mesg, 3); 
			die;
		} else {
			debugadd(10,"START_TLS was successfull.\n");
		}
	} 

	if ($mode =~ /anonymous/i) {
		$mesg = $ldap->bind();
		$status = "binding anonymously";
	} else {
		$mesg = $ldap->bind( "$ADMIN_DN", password => $ADMIN_PW );
		if ($DEBUG_PASSWORD) {
			$status = "binding as \"$ADMIN_DN\" with password \"$ADMIN_PW\"";
		} else {
			$status = "binding as \"$ADMIN_DN\" with password xxx (not logged)";
		}
	}
	if ($mesg->code) {
		ldap_status($status, $mesg, -1); 
		die;
	} else {
		debugadd(10,"$status was successful.\n");
	}
	return $ldap;
} 

sub ldap_anon_bind() {
	return ldap_bind("anonymous");
}

sub ldap_unbind ($) {

	my $hd = shift;

	my $status = "unbinding";
	$mesg = $hd->unbind;
	if ($mesg->code) {
		ldap_status($status, $mesg, 3); 
		die;
	} else {
		debug(10,"$status was successful.\n");
	}
}


sub ldap_status {

	my ($from, $mesg, $tmp) = @_;
	$tmp = $tmp || "5";
	debug($tmp,"\twhile:\t\t$from\n");
	debugadd($tmp,"\terror:\t\t".$mesg->error()."\n");
	debugadd($tmp,"\treturn code:\t".$mesg->code."\n");
	debugadd($tmp,"\tmessage:\t".ldap_error_name($mesg->code).": \n");
	debugadd($tmp,"\t\t\t".ldap_error_text($mesg->code));
	debugadd($tmp,"\tmessageID:\t".$mesg->mesg_id."\n");
	debugadd($tmp,"\tdn:\t\t".$mesg->dn."\n");
}

sub ldap_update ($$$$) {

	my $hd 		= shift;
	my $entry	= shift;
	my $critic 	= shift || "critical"; # critical update ?
	my $status_mesg = shift || "updating (modifying)";

	my $dn = $entry->dn;
	debug(5,"updating dn:\t\"$dn\"\n");

	my $mesg_update = $entry->update( $hd );

	if ($mesg_update->code) {
		ldap_status("$status_mesg", $mesg_update, 3);
		if ($critic eq "critical") {
			# now we really die during an update
			debugadd(3,"we die. error during an update-attempt\n");
			die;
		} elsif ($critic eq "noncritical") {
			# now we do not die
			debugadd(5,"error, but we do not die\n");
			return False;
		}

	} 
	
	ldap_status("$status_mesg", $mesg_update);
	debugadd(5,"update was successful.\n");
	return True;
}


#######################
# ldap uid/gid handling
#######################

sub valid_id_range ($) {

	my $range = shift;
	
	if ($range =~ /^\d+-\d+$/ ) {
		return True;
	}
	
	debug(0,"invalid id-range\n");
	return False;
}

sub get_maxid_val ($) {

	my $entry = shift || goto failed;
	my $val = $entry->get_value( 'description' ) || goto failed;
	if ($val =~ /^\d+$/ ) {
		return $val;
	}
 failed:
	debug(0,"invalid maxid value\n");
	return undef;
}

sub check_maxid_watermark ($$) {

	my $current_id = shift;
	my $max_limit  = shift;
	if ( $current_id >= $max_limit ) {
		debug(0,"cannot add new accounts.\n");
		debugadd(0,"highest {u|g}id already reached. exiting\n");
		return False;
	}
	return True;
}

sub check_nss_uses_id ($$) {
	
	my $id = shift;
	my $id_type = shift;
	
	if ($id_type eq "user") {
		if (getpwuid($id)) {
			debug(0,"uid $id is already in use\n");
			return True;
		}
	} elsif ($id_type eq "group") {
		if (getgrgid($id)) {
			debug(0,"gid $id is already in use\n");
			return True;
		}
	}
	return False;
}

sub ldap_id ($$) {

	my $hd = shift;
	my $id = shift;
	
	my ($ldap_attr,$ID_RANGE,$ldap_uid_attr,$user_or_group,$oc,$NEW_MAXID);

	if (!$USE_MAXID) {
		return undef;
	}

	if ( $id =~ /uid/i ) {
		$ID_RANGE 	= $UID_RANGE;
		$ldap_uid_attr 	= "maxuid";
		$user_or_group 	= "user";
		$oc 		= "posixAccount";
		$ldap_attr 	= "uidNumber";
	} elsif ( $id =~ /gid/i ) {
		$ID_RANGE 	= $GID_RANGE;
		$ldap_uid_attr 	= "maxgid";
		$user_or_group 	= "group";
		$oc 		= "posixGroup";
		$ldap_attr 	= "gidNumber";
	} else {
		debug(3,"unknown id-type",$id,"\n");
		return undef;
	}

	if (!valid_id_range($ID_RANGE)) {
		return undef;
	}
	
	my ($MIN_ID,$MAX_ID) = split(/-/,$ID_RANGE);

	my $INIT_MAXID = $MIN_ID; 

	debug(5,"got order to generate a new $ldap_attr\n");

	# look for maxid in ldap
	my $num_result = ldap_search($hd, $SUFFIX, "(&(objectclass=top)(uid=$ldap_uid_attr))");
	if ($num_result > 1) {
		debug(0,"more then one MAXUID-object found. exisiting\n");
		return undef;
	}
	
	if ($num_result == 1) {

		# if it is there, get the value
		my $LDAP_MAXID = get_maxid_val($entry);
		if (!$LDAP_MAXID) {
			return undef;
		}

		# check maxid
		if (!check_maxid_watermark($LDAP_MAXID, $MAX_ID)) {
			return undef;
		}

		# increase
		$NEW_MAXID = $LDAP_MAXID + 1;

		debug(5,"MAXID ($id) is currently $LDAP_MAXID, next $user_or_group will have $NEW_MAXID\n");

		# store that entry
		my $maxid_entry = $entry;

		# double-check if no one uses this id
		my $found_id = 0;
		until ( $found_id ) {

			if ( ldap_search($hd, $SUFFIX, "(&(objectclass=$oc)($ldap_attr=$NEW_MAXID))") ||
			     check_nss_uses_id($NEW_MAXID, $user_or_group)) {
				debug(5,"bad. $ldap_attr $NEW_MAXID is already in use.\n");
				# possibly try to find the next
				++$NEW_MAXID;
				debug(5,"trying $NEW_MAXID now\n");

				# check maxid
				if (!check_maxid_watermark($LDAP_MAXID, $MAX_ID)) {
					return undef;
				}

			} else {

				# check maxid
				if (!check_maxid_watermark($LDAP_MAXID, $MAX_ID)) {
					return undef;
				}

				# and update
				debug(5,"writing updated MAXID ($id) $NEW_MAXID to ldap-tree\n");
				$entry = $maxid_entry;
				my $mesg = $maxid_entry->replace( 'description' => $NEW_MAXID );
				ldap_update($hd, $entry, "critical", "updating maxid ($id)");
				debug(5,"updated MAXID ($id)\n");

				# and use it
				if ( $id =~ /uid|gid/i ) {
					return $NEW_MAXID;
				}
				$found_id = 1;
			}
		}

		return undef;

	} 
	
	# if not there, initialize it:
	debug(0,"initialising maxid for $id\n");
	$INIT_MAXID = "$MIN_ID";

	# double-check if no one uses our uid
	my $found_id = 0;
	until ( $found_id ) {

		if ( ldap_search($hd, $SUFFIX, "(&(objectclass=$oc)($ldap_attr=$INIT_MAXID))") ||
		     check_nss_uses_id($INIT_MAXID, $user_or_group)) {

			# check maxid
			if (!check_maxid_watermark($INIT_MAXID, $MAX_ID)) {
				return undef;
			}

			debug(5,"bad. $ldap_attr $INIT_MAXID is already in use.\n");
			# possibly try to find the next
			++$INIT_MAXID;
			debugadd(5,"trying $INIT_MAXID for $id now\n");

		} else {

			# check maxid
			if (!check_maxid_watermark($INIT_MAXID, $MAX_ID)) {
				return undef;
			}

			debug(5,"writing updated INIT_MAXID $INIT_MAXID (for $id) to ldap-tree\n");
			$entry = Net::LDAP::Entry->new;
			my $dn = "uid=$ldap_uid_attr,$SUFFIX";
			debugadd(5,"++\tadding new object with:\tdn $dn\n");
			$entry->dn( "$dn" );
			$entry->add( 'objectclass' => [qw(top account)]);
			$entry->add( 'description' => $INIT_MAXID);
			$entry->add( 'uid' => $ldap_uid_attr);

			ldap_update($hd, $entry, "critical", "adding maxid for $id");
			debugadd(5,"updated MAXID for $id\n");

			# and use it
			if ( $id =~ /uid|gid/i ) {
				return $INIT_MAXID;
			}

			$found_id = 1;
		}

	}
	return undef;
} 

sub ldap_uid ($) {
	my $hd = shift;
	return ldap_id($hd, "uid");
}

sub ldap_gid ($) {
	my $hd = shift;
	return ldap_id($hd, "gid");
}

sub ldap_init {

	my $hd = shift;

	# FIXME: this is rubbish...
	debug(3,"initializing your ldap-tree, some data will be deleted!\n");

	my %ous = (
		$SUFFIX 	=> 0, 
		$SUFFIX_GROUPS	=> 0,
		$SUFFIX_USERS	=> 0,
		$SUFFIX_MACHINES => 0,
		$SUFFIX_IDMAP	=> 0);

	my $result;
	
	foreach my $ou (sort keys %ous) {
	
		next if (!$ou);
		
		next if ($ou =~ /^$SUFFIX$/);

		my $tmp = $ou;
		$tmp =~ s/^ou=//i;
		$tmp =~ s/,.*//i;
		debug(3,"adding: \t $ou\n");
		$result = $hd->add ( 	"$ou",
			attrs => [ 'objectClass' => 'organizationalUnit', 'ou' => "$tmp" ]);
		ldap_status("creating ous", $result, -1);
	}
}


sub print_smb_conf ($) {

	my $hd = shift;

	# print what we got
	print "\nRunning-mode:\t\t\t$opt_mode\n";
	printf "\nLDAP-mode:\t\t\tLDAP over %s\n", ($USE_LDAPI && $HAVE_LDAPI) ? "IPC ($LDAPI_SOCKET)" : "TCP/IP";
	print "\nIdentified Samba Version:\t$SAMBA_SCHEMA_VERSION\n";
	print "\nAutodetected from [$SMB_CONF]:\n";
	print "\tldap admin dn:\t\t$ADMIN_DN\n";
	print "\tldap suffix:\t\t$SUFFIX\n";
	print "\tldap machine suffix:\t$SUFFIX_MACHINES\n" if ($SUFFIX_MACHINES);
	print "\tldap user suffix:\t$SUFFIX_USERS\n" if ($SUFFIX_USERS);
	print "\tldap group suffix:\t$SUFFIX_GROUPS\n" if ($SUFFIX_GROUPS);
	print "\tldap idmap suffix:\t$SUFFIX_IDMAP\n" if ($SUFFIX_IDMAP);
	print "\tldap server:\t\t$LDAP_SERVER\n";
	print "\tldap port:\t\t$LDAP_PORT\n";
	print "\tldap ssl:\t\t$LDAP_SSL\n";

	print "\nAutodetected from [$confs{'secrets.tdb'}]:\n";
	print "\tldap admin password:\t$ADMIN_PW\n";

	print "\nAutodetected binaries and configuration files:\n";
	foreach my $prog ( @progs ) {
		printf("\t%-23s %s\n", $prog, $progs{$prog});
	}
	foreach my $conf ( @confs ) {
		printf("\t%-23s %s\n", $conf, $confs{$conf});
	}

	print "\nwill use:\nldap group suffix:\t$SUFFIX_GROUPS\n" if ($SAMBA_22);

	if ( $USE_MAXID ) {
		print "\nMAXID-settings stored in ldap:\n";
		if ( ldap_search($hd, "$SUFFIX","(&(objectclass=top)(uid=maxuid))") ) {
			my $MAXUID = get_maxid_val($entry);
			if (!$MAXUID) {
				return;
			}
			print "\tmaxUid:\t\t\t$MAXUID\n";
			if (!valid_id_range($UID_RANGE)) {
				return;
			}
			my ($MIN_UID,$MAX_UID) = split(/-/,$UID_RANGE);
			print "\tuid range:\t\t$UID_RANGE\n";
			print "\tyou can add:\t\t",$MAX_UID-$MAXUID, " user accounts\n";
		}
		if ( ldap_search($hd, "$SUFFIX","(&(objectclass=top)(uid=maxgid))") ) {
			my $MAXGID = get_maxid_val($entry);
			if (!$MAXGID) {
				return;
			}
			print "\tmaxGid:\t\t\t$MAXGID\n";
			if (!valid_id_range($GID_RANGE)) {
				return;
			}
			my ($MIN_GID,$MAX_GID) = split(/-/,$GID_RANGE);
			print "\tgid range:\t\t$GID_RANGE\n";
			print "\tyou can add:\t\t",$MAX_GID-$MAXGID, " group accounts\n";
		}
		print "\n";
	}
}


sub debug_internal {

	my $loglevel = shift;
	my $add = shift;
	my $msg = "@_";

	my ($package, $filename, $line, $subroutine) = caller(0);
	my ($p_package, $p_filename, $p_line, $p_subroutine) = caller(1);
	my ($pp_package, $pp_filename, $pp_line, $pp_subroutine) = caller(2);
	my $sub = $pp_subroutine || $p_subroutine || "";

	$sub =~ s/^.*:://g; 
	$filename =~ s/.*\///g;
	my $date = strftime "%b %e %H:%M:%S", localtime;
	my $prog = "$filename:$sub($line)";

	if ( $DEBUGLEVEL >= $loglevel ) {

		# write to STDOUT and with a timestamp to our logfile
		if ($USE_LOGFILE) {
			select(LOGFILE_HANDLE);
			$~ = ($add?"LOGFILE_HANDLE_ADD":"LOGFILE_HANDLE");
			write(LOGFILE_HANDLE);
		}

		if ($USE_SYSLOG) {
			syslog('info', $msg);
		}

		select(STDOUT);
		my $ofh = select(STDOUT);
		if ($DEBUGLEVEL == 0) {
			$~ = "DEBUG_STDOUT_0";
		} else {
			$~ = "DEBUG_STDOUT";
		}
		select($ofh);
		write();

	} elsif ( $loglevel <= 10) {
		if ($USE_LOGFILE) {
			# write to STDOUT and with a timestamp to our logfile
			select(LOGFILE_HANDLE);
			$~ = ($add?"LOGFILE_HANDLE_ADD":"LOGFILE_HANDLE");
			write(LOGFILE_HANDLE);
			select(STDOUT);
		}
		if ($USE_SYSLOG) {
			syslog('info', $msg);
		}
	}
	
format LOGFILE_HANDLE_ADD =
   @*
$msg
.
format LOGFILE_HANDLE =
[@<<<<<<<<<<<<<<] @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 
$date, $prog
   @*
$msg
.
format DEBUG_STDOUT =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @*
$prog, $msg
.
format DEBUG_STDOUT_0 =
@*
$msg
.
}

sub debugadd {

	my $loglevel = shift;
	my @msg = @_;
	debug_internal($loglevel, "1", @msg);
}

sub debug {

	my $loglevel = shift;
	my @msg = @_;
	debug_internal($loglevel, 0, @msg);
}

sub umlaut2ascii {

	my $name = shift || "";

	# german characters
	$name =~ s/ä/ae/g ;
	$name =~ s/Ä/Ae/g ;
	$name =~ s/ü/ue/g ;
	$name =~ s/Ü/Ue/g ;
	$name =~ s/ö/oe/g ;
	$name =~ s/Ö/Oe/g ;
	$name =~ s/ß/ss/g ;

=cut
	# strange characters :)
	$name =~ s/�|�|�|�/a/g ;
	$name =~ s/�|�|�/A/g ;
	$name =~ s/�|Ã�|�/e/g ;
	$name =~ s/�|�|�/E/g ;
	$name =~ s/�|�|�/i/g ;
	$name =~ s/�|�|�/I/g ;
	$name =~ s/Ã³/o/g ;
	$name =~ s/�|Ó|�|�/O/g ;
	$name =~ s/�/c/g ;
=cut
	return $name;

}


sub init_logging {

	if ($opt_quiet) {
		$DEBUGLEVEL = "-1";
	}
	if ($USE_SYSLOG) {
		openlog("ldapsmb", "", "LOG_USER");
		return;
	}
	return if (! $USE_LOGFILE);
	
	# open the logfile, or create it if not there
	# FIXME: what to do if it exists, but we can't write ??
	unless (-w $LOGFILE_NAME) {
		open (LOGFILE_HANDLE, ">$LOGFILE_NAME") || die "cannot create logfile: $!";
	}
	open (LOGFILE_HANDLE, ">>$LOGFILE_NAME") || die "could not append to logfile: $!";
	debug(1,"ldapsmb version $VERSION\n");
	debugadd(1,"   Copyright 2003-2005 Günther Deschner, Lars Müller\n");

}


sub close_logging {

	if ($USE_LOGFILE) {
		close (LOGFILE_HANDLE) || die "could not close file: $!";
	}
	if ($USE_SYSLOG) {
		closelog();
	}
}

sub set_on_find($$$) {

	my ($line, $parm, $VAR) = @_;
	if ($line =~ /$parm/ && $VAR eq "") {
		my ($dummy,$FOUND_VAR) = split(/.*=\s+/,$line);
		debugadd(10,"autodetected \"$parm\":\t\t$FOUND_VAR\n");
		return $FOUND_VAR;
	}
	return $VAR;
}
		
sub parse_conf_file($) {

	my $handle = shift;
	my $dummy;

	# open handle and read
	open (DATA, "$handle") || die "could not open \"$handle\": $!";
	while (my $line = <DATA>) {
		next if $line =~ /^#|^;/;
		chomp($line);
		
		$ADMIN_DN 	 = set_on_find($line, "ldap admin dn", $ADMIN_DN);
		$SUFFIX		 = set_on_find($line, "ldap suffix", $SUFFIX);
		$SUFFIX_MACHINES = set_on_find($line, "ldap machine suffix", $SUFFIX_MACHINES);
		$SUFFIX_USERS	 = set_on_find($line, "ldap user suffix", $SUFFIX_USERS);
		$SUFFIX_GROUPS	 = set_on_find($line, "ldap group suffix", $SUFFIX_GROUPS);
		$SUFFIX_IDMAP	 = set_on_find($line, "ldap idmap suffix", $SUFFIX_IDMAP);
		$LDAP_SSL	 = set_on_find($line, "ldap ssl", $LDAP_SSL);
		$ADMIN_PW	 = set_on_find($line, "ldap admin password", $ADMIN_PW);

		# the following two options are only in 2.2.x
		if ($SAMBA_22) {
			$LDAP_SERVER = set_on_find($line, "ldap server", $LDAP_SERVER);
			$LDAP_PORT   = set_on_find($line, "ldap port", $LDAP_PORT);
		}
		# the following option is in HEAD/3.0 only
		# FIXME: handle more ldapsam-backends
		# FIXME: accept predefined variables (see above)
		if ($SAMBA_30 && ($line =~ /passdb backend/)) {
			
			my $tmp;
				
			($dummy,$passdb) = split(/.*=\s+/,$line);

			#remove leading backends
			($dummy,$tmp) = split(/ldapsam:/, $passdb);

			#remove trailing backends
			$tmp =~ s/(,\s+|\s+).*//g;

			my ($proto,@str) = split(/:/,$tmp);

			# remove leading slashes
			$str[0] =~ s#^//##;

			if ($proto eq "ldapi") {
				$LDAP_SERVER = "";
				$LDAP_PORT = "";
				goto found_passdb;
			} elsif ($proto eq "ldap") {
				($LDAP_SERVER,$LDAP_PORT) = @str;
				$LDAP_PORT = $LDAP_PORT  || "389";
			} elsif ($proto eq "ldaps") {
				($LDAP_SERVER,$LDAP_PORT) = @str;
				$LDAP_PORT = $LDAP_PORT  || "636";
			}
			$LDAP_SERVER = set_default_on_empty($LDAP_SERVER, 
							    "ldap server", 
							    "localhost", "localhost");
 found_passdb:
			debugadd(10,"autodetected \"ldap server\":\t\t\"$LDAP_SERVER\"\n");
			debugadd(10,"autodetected \"ldap port\":\t\t\"$LDAP_PORT\"\n");
		} 
	}
	close(DATA);
}

sub set_default_on_empty($$$$) {
	my ($VAR, $str, $default, $default_string) = @_;
	if (! $VAR || ($VAR eq "")) {
		debugadd(10,"no \"$str\" available. Using value of $default_string instead.\n");
		return $default;
	}
	return $VAR;
}

# parse the smb.conf
sub parse_smbconf {

	my $dummy;

	if ( -e $LDAPSMBRC) {
		debug(3,"parsing [$LDAPSMBRC]\n");
		parse_conf_file("< $LDAPSMBRC");
	}

	# extra conf file?
	if ($opt_smbconfig) {
		debug(3,"parsing [$SMB_CONF]\n");
		parse_conf_file("< $SMB_CONF");
	} else {
		debug(3,"parsing [testparm]\n");
		parse_conf_file("$progs{testparm} -s 2> /dev/null |");
	}

	if ($SAMBA_30 && !$passdb) {
		debug(3,"no \"passdb backend\" in your configuration. using defaults for LDAP_SERVER and LDAP_PORT\n");
	}

	# exit on missing critical info
	if (!$SUFFIX) {
		debug(0,"no \"ldap suffix\" defined. exiting.\n");
		return False;
	}

	if (!$ADMIN_DN) {
		debug(0,"no \"ldap admin dn\" defined. exiting.\n");
		return False;
	}	

	$SUFFIX_USERS = set_default_on_empty($SUFFIX_USERS, "ldap user suffix", $SUFFIX, "ldap suffix");
	$SUFFIX_GROUPS = set_default_on_empty($SUFFIX_GROUPS, "ldap group suffix", $SUFFIX, "ldap suffix");
	$SUFFIX_MACHINES = set_default_on_empty($SUFFIX_MACHINES, "ldap machine suffix", $SUFFIX, "ldap suffix");

	# have start_tls ?
	if ( $LDAP_SSL =~ /start*tls/i ) {
		$START_TLS = 1;
	}

	# build the groups suffix
#	$SUFFIX_GROUPS = $SUFFIX_GROUPS || "ou=Groups,$SUFFIX";
#	debug(10,"will search/store/modify groups in:\t$SUFFIX_GROUPS\n");

	$SUFFIX_USERS = check_suffix($SUFFIX_USERS);
	$SUFFIX_GROUPS = check_suffix($SUFFIX_GROUPS);
	$SUFFIX_MACHINES = check_suffix($SUFFIX_MACHINES);

	return True;
}

sub check_suffix {
	
	my $suffix = shift;
	return sprintf("%s", $suffix !~ /$SUFFIX/i ? "$suffix, $SUFFIX" : "$suffix" );
}


sub find_adminpwd_in_tdb {

	# why don't we just read with tdbtool ?

	my $key_pattern;
	my $match_pattern;
	my $subst_pattern;

	my ($dummy, $tdb_pwd);

	if ($opt_mode ne "local") {
		debug(10,"cannot retrieve password from secrets.tdb when not in local mode\n");
		goto failed;
	}

	if (! -e $confs{'secrets.tdb'} ) {
		goto failed;
	}

	if ( $SAMBA_22 ) {
		$key_pattern = "(uid|cn)=";
		$match_pattern = "\/";
		$subst_pattern = ",";
	}

	if ( $SAMBA_30 ) {
		$key_pattern = "SECRETS/LDAP_BIND_PW/";
		$match_pattern = $key_pattern;
		$subst_pattern = "";
	}
	my $cnt = 0;

	open (DATA, "$progs{tdbdump} $confs{'secrets.tdb'} 2> /dev/null|") || die "could not open $confs{'secrets.tdb'}: $!";
	while (my $line = <DATA>) {
		debug(20,"line is: $line\n");

		if ($line =~ /^key.*= \"$key_pattern/) {
			my ($dummy,$tmp) = split(/"/,$line);
			$tmp =~ s/$match_pattern/$subst_pattern/g;
			if ($tmp eq $ADMIN_DN) {
				$cnt = 1; 
				next;
			} else {
				debug(3,"strange dn: \"$tmp\"\n");
			}
		} elsif ($cnt == 1) {
			($dummy,$tdb_pwd) = split(/\"/,$line);
			$tdb_pwd =~ s/\\.*$//g;
			if ($DEBUG_PASSWORD) {
				debug(10,"autodetected \"ldap admin password\":\t$tdb_pwd\n");
			} else {
				debug(10,"autodetected \"ldap admin password\":\tnot logged...\n");
			}
			return $tdb_pwd;
		} 
	}
	close(DATA);
	
	if ( $cnt == 0 ) {
		debug(0,"sorry. could not find your password in \"secrets.tdb\"\n");
		debugadd(0,"either you set it in [$0] or you use \"smbpasswd -w pwd\" if you are running [$0] in local mode.\n");
		debugadd(0,"exiting.\n");
	}

 failed:
	return undef;
}

#@ gets a list of binaries
#@ returns != 0 on failure
sub find_progs (@) {

	my @progs = @_;

	my $path = sprintf("%s:%s", $ENV{PATH}, "/usr/sbin");
	my @paths = split(/:/, $path);
	foreach my $bin (@progs) {
		foreach my $path (@paths) {
			if ( -e "$path/$bin" ) {
				$progs{$bin} = "$path/$bin";
				last;
			}
		}
		if (! $progs{$bin} ) {
			debug(0,"could not find $bin\n");
			return False;
		}
	}
	return True;
}

sub find_confs (@) {

	my @confs = @_;

	my @paths = qw( /etc /etc/samba );
	foreach my $conf (@confs) {
		foreach my $path (@paths) {
			if ( -e "$path/$conf" ) {
				$confs{$conf} = "$path/$conf";
				last;
			}
		}
		if (! $confs{$conf}) {
			debug(0,"could not find $conf\n");
			return False;
		}
	}
	return True;
}


sub get_smbd_samba_ver ($) {

	my $smbd = shift;
	
	my $samba_ver = `$smbd -V`;	
	$samba_ver =~ s/^version //i;

	if ( $samba_ver =~ /^2/ ) {
		$SAMBA_SMBD_VERSION = "2_2";
	} elsif ( $samba_ver =~ /^3|3\.0/ ) {
		$SAMBA_SMBD_VERSION = "3_0";
	} elsif ( $samba_ver =~ /HEAD/ ) {
		$SAMBA_SMBD_VERSION = "HEAD";
	} elsif ( $samba_ver =~ /tng/i ) {
		debug(0,"Samba TNG is not yet supported. exiting.\n");
		return False;
	} else {
		debug(0,"unknown samba version. exiting.\n");
		return False;
	}
	return True;
}


sub check_sane_name {

	my $tmp = shift;

	if ( $tmp =~ /\*/ ) { 
		return "an asterik is a wildcard character" 
	};
	if ( $tmp =~ /\./ ) { 
		return "dots can possibly cause problems if you want to do mail-routing later" 
	};
	if (! $HAVE_UTF8) {
		if ( $tmp =~ /ä|ö|ü|ß/i ) { 
			return "german umlauts are depreciated unless your system is fully utf8-aware" 
		};
	}
	if ( $tmp =~ /\$/ ) { 
		return "the \$-sign should stay reserved for workstations" 
	};
	if ( $tmp =~ /\s+/ ) { 
		return "white-space in names can cause some applications to behave oddly" 
	};
}


sub check_rid_allocation {

	my $uidNumber 	= shift;
	my $gidNumber 	= shift;
	my $rid 	= shift;
	my $primaryGroupID = shift;

	if ( $rid != $uidNumber*2+1000 ) {
		return "warning: your user-RID does not match.\nthis account may be broken!"
	}

	if ( $primaryGroupID != $gidNumber*2+1001 ) {
		return "warning: your group-RID does not match.\nthis account may be broken!"
	}
}


sub get_schema_samba_ver($) {

	my $hd = shift;
	my $schema = $hd->schema();
	
	if ( $schema->attribute( 'sambaSID' ) ) {
		return "3_0"; 
	} elsif ( $schema->attribute( 'rid' ) ) {
		return "2_2";
	} else {
		return "unknown samba-version";
	}
}


sub detect_mode($) {

	my $mode = shift;

	$mode = $opt_mode || "local";
	if ( $mode =~ /(local|remote)/ ) {
		$opt_mode = $mode;
	} else {
		debug(0,"unknown mode choosen: $mode\n");
		return False;
	}
	return True;
}


sub prepare_account($) {

	my $hd = shift;
	
	if ($opt_gid) {
		$opt_gid = get_gid($hd, $opt_gid);
		if (!$opt_gid) {
			return False;
		}
	}

	# handle umlauts (unless utf8 is widly adopted...)
	if ($opt_user) {
		if (! $opt_force && $opt_add ) {
			my $ret = check_sane_name($opt_user);
			if ($ret) { 
				debug(0,"your username ($opt_user) has strange characters that might cause problems:\n");
				debug(0,"\t$ret\n");
				debug(0,"you can force account creation with -f\n");
				return False;
			}
		}

		if (! $HAVE_UTF8) {
			my $tmp = $opt_user;
			from_to($opt_user, "utf-8", "iso-8859-1" );
			$opt_user = encode_utf8($opt_user);
			$ascii_user = umlaut2ascii($tmp);
		}
	}

	if ($opt_group) {
		if (! $opt_force && $opt_add) {
			my $ret = check_sane_name($opt_group);
			if ($ret) { 
				debug(0,"your groupname ($opt_group) has strange characters that might cause problems:\n");
				debug(0,"\t$ret\n");
				debug(0,"you can force account creation with -f\n");
				return False;
			}
		}
		if (! $HAVE_UTF8) {
			my $tmp = $opt_group;
			from_to($opt_group, "utf-8", "iso-8859-1" );
			$opt_group 	= encode_utf8($opt_group);
			$ascii_group = umlaut2ascii($tmp);
		}
	}
	return True;
}


sub get_gid ($) {

	# handle gid
	my $hd = shift;
	my $gid = shift;
	my $gid_ret;

	if ( defined($gid) && ( $gid =~ /^\d+$/ ) ) {
		return $gid;
	}
	
	debug(5,"gid $gid is not numeric. converting\n");
	my $num_result = ldap_search($hd, $SUFFIX,"(&(objectclass=posixGroup)(cn=$gid))");
	if ($num_result > 1) {
		debug(0,"more then one groups with name [$gid] found\n");
		return undef;
	}
	if ($num_result == 1) {
		return $entry->get_value('gidNumber') || undef;
	}
	
	$gid_ret = getgrnam($gid);
	if ($gid_ret || defined($gid_ret)) {
		debug(5,"ok. group [$gid] exists somewhere else in the NSS.\n");
		return $gid_ret;
	} 
	debug(0,"could not resolve group: [$gid] to numeric gid\n");
	return undef;
}

# @DOCS@
