#!/usr/bin/perl -w
#
# Copyright (c) 2003-2004 SuSE Linux AG, Nuernberg, Germany.
# All rights reserved
#
# written by:	Guenther Deschner <gd@suse.de>
#		Lars Mueller <lmuelle@suse.de>
#
# $Id: ldapsmb,v 1.50 2003/06/18 08:27:18 gd Exp $

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);

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;
#}

my ($TDB_PW,$SMB_OC,$LOCAL_MODE,$REMOTE_MODE);
my ($uidNumber,$gidNumber);
my ($dummy,$line,$tmp,$ldap,$debuglevel,$passdb);
our ($mesg,$entry);
my %progs;
my %confs;

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

my ($force,$help,$user,$passwd,$comment,$loginShell,$homedir,$makehomedir,$skeldir,$ntgroup,$group,$add,$mod,$delete,$list,$join,$remove,$init,$workstation,$config,$smbacct,$smbconfig,$verbose,$ascii_user,$uid,$gid,$ascii_group,$quiet,$mode,$raw,$version) = 0;

GetOptions(
	'add|a'			=> \$add,
	'comment|c=s'		=> \$comment,
	'config'		=> \$config,
	'debug=s'		=> \$debuglevel,
	'delete|d'		=> \$delete,
	'force'			=> \$force,
	'gid:s'			=> \$gid,
	'group|g:s'		=> \$group,
	'help|?|h'		=> \$help, 
	'homedir=s'		=> \$homedir,
	'init'			=> \$init,
	'join|j'		=> \$join,
	'list|l'		=> \$list,
	'makehomedir'		=> \$makehomedir,
	'modify|m'		=> \$mod,
	'mode=s'		=> \$mode,
	'ntgroup=s'		=> \$ntgroup,
	'passwd|p=s'		=> \$passwd,
	'quiet'			=> \$quiet,
	'raw'			=> \$raw,
	'remove|r'		=> \$remove,
	'shell=s'		=> \$loginShell,
	'skeldir|k=s'		=> \$skeldir,
	'smbacct|s'		=> \$smbacct,
	'smbconf=s'		=> \$smbconfig,
	'uid=i'			=> \$uid,
	'user|u:s'		=> \$user,
	'verbose|v'		=> \$verbose,
	'version'		=> \$version,
	'workstation|wks:s'	=> \$workstation,
	) or pod2usage(2);

pod2usage(1) if $help;


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

my $VERSION		=	"1.31";
my $LOGFILE_NAME	=	"/var/log/samba/log.ldapsmb";
my $DEBUGLEVEL 		= 	$debuglevel || "0" ;
my $DEBUG_PASSWORD 	= 	0;
$DEBUGLEVEL		=	"-1" if ($quiet); 
init_logfile();

my $LDAPSMBRC		=	$ENV{'HOME'}."/.ldapsmbrc";
my $MODE		= 	$mode || "local";
detect_mode();

# leave empty for (expensive) autodetection
my $ADMIN_DN		=	"";
my $ADMIN_PW		=	"";
my $LDAP_PORT		=	"";
my $LDAP_SERVER		=	"";
my $LDAP_SSL		=	"";
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-2000";
my $GID_RANGE		=	"1000-2000";
my $PRIMARYGIDNUMBER	=	"100";	
my $gecos		=	"SMBLDAP-User";
my $wksGidNumber	= 	"65534" || $gid;

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

# 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 @progs = qw( tdbdump pdbedit smbpasswd smbd testparm );
my @confs = qw( smb.conf secrets.tdb );

sub find_progs (@);
sub find_confs (@);
sub get_smbd_samba_ver ($);
sub ldap_delete_internal($$$);
sub get_gid ($);

my $SAMBA_SMBD_VERSION = "";
if ( $LOCAL_MODE ) {
	if (find_progs(@progs)) {
		print "missing binaries\n";
		exit 1;
	}
	if (find_confs(@confs)) {
		print "missing configuration\n";
		exit 1;
	}
	$SMB_CONF		=	$confs{"smb.conf"} || "";
	$SAMBA_SMBD_VERSION	=	get_smbd_samba_ver($progs{smbd});
}
my $SAMBA_22 = 1 	if ( $SAMBA_SMBD_VERSION eq "2_2" );
my $SAMBA_30 = 1	if ( $SAMBA_SMBD_VERSION eq "3_0" or "HEAD" );
my $net				=	find_progs("net") if ($SAMBA_30);


########################
# do some initialisation
########################

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

# what is our configfile
if ( $LOCAL_MODE ) {
	$SMB_CONF = $smbconfig || $SMB_CONF;
} elsif ( $REMOTE_MODE ) {
	if ( ! $smbconfig || ! -e $smbconfig ) {
		debug(0,"running in remote-mode: need a smb.conf.\n");
		debugadd(0,"please use --smbconf=/my/path/smb.conf\n");
		exit 1;
	}
	$SMB_CONF = $smbconfig;
}

# get the admin password
parse_smbconf();
if ( ($> eq "0") && ( $ADMIN_PW eq "") ) {
	find_adminpw();
} elsif ($ADMIN_PW eq "")  {
	debug(0,"sorry. you're not root. please set up your password manually in $0\n");
}

$ADMIN_PW = $TDB_PW;


# some defaults
$LDAP_PORT 	= $LDAP_PORT 	|| "389";
$LDAP_SERVER 	= $LDAP_SERVER 	|| "localhost";
$LDAP_SSL 	= $LDAP_SSL 	|| "no";

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

# try to detect samba-version by schema-detection
ldap_bind("anonymous");
my $SAMBA_SCHEMA_VERSION = get_schema_samba_ver();
ldap_unbind();
$SMB_OC = "sambaSamAccount" if ($SAMBA_30);
$SMB_OC = "sambaAccount" if ($SAMBA_22);



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

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

if ( $mod ) {
	if ( !$user && !$group && !$workstation ) {
		debug(0,"error: what do you want to modify?\n");
		debugadd(0,"choose between: user, machine or group. exiting.\n");
		exit 1;
	}
}

if ( $delete ) {
	if ( !$user && !$group && !$workstation ) {
		debug(0,"error: what do you want to delete?\n");
		debugadd(0,"choose between: user, machine or group. exiting.\n");
		exit 1;
	}
}

if ( $list ) {
	if ( !defined($user) && !defined($group) && !defined($workstation) ) {
		debug(0,"error: what do you want to list?\n");
		debugadd(0,"choose between: user, machine or group. exiting.\n");
		exit 1;
	}
}

if ( $join ) {
	if ( !$user && !$group ) {
		debug(0,"error: you only can join a user to a group. exiting.\n");
		exit 1;
	}
}

if ( $remove ) {
	if ( !$user && !$group ) {
		debug(0,"error: you only can remove a user from a group. exiting.\n");
		exit 1;
	}
}

# show config
if ( $config ) {
	ldap_bind("anonymous");
	print_smb_conf();
	ldap_unbind();
	exit 0;
}


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


# for the rest...
ldap_bind();

prepare_account();

if ($gid) {
	$gid = get_gid($gid);
}

# add user
if ( $add && $user ) {
	my $USER_FILTER="(&(objectclass=posixAccount)(uid=$user))";
	if ( ldap_search($SUFFIX_USERS,$USER_FILTER) ) {
		if ( $smbacct ) {
			debug(0,"adding sambaaccount to existing user: $user\n");
			ldap_smbuser_add($user);
			exit 0;
		}
		debug(0,"user $user already exists. exiting\n");
		exit 1;
	} else {
		if ($USEMAXID && !$uid) { 
			ldap_id("uid"); 
		} elsif ( !$uid ) {
			debug(0,"please define uidNumber with --uid=uidNumber\n");
			exit 1;
		} elsif ( $uid ) {
			$uidNumber = $uid;
		}
		debug(0,"adding user: $user (with uidNumber: $uidNumber)\n");
		ldap_posixuser_add($user);
		if ( $smbacct ) {
			debug(0,"adding sambaaccount: $user\n");
			ldap_smbuser_add($user);
		}
	}
};


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

	my $USER_FILTER="(&(objectclass=posixAccount)(uid=$user))";
	if ( ldap_search($SUFFIX_USERS,$USER_FILTER) ) {
		debug(0,"modifying user: $user\n");
		ldap_usermodify($user);
	} elsif ( ldap_search($SUFFIX_MACHINES,$USER_FILTER) ) {
		debug(0,"modifying workstation: $user\n");
		ldap_usermodify($user);
	} else {
		debug(0,"no such account $user to modify\n");
		exit 1;
	}
};


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

	my $no_user = $user;
	$user = $user || "*";
	my $USER_FILTER="(&(objectclass=*)(uid=$user))";
	
	if ( ldap_search($SUFFIX_USERS,$USER_FILTER) ) {
		debug(5,"list user: $user\n");
		if (defined($smbacct)) {
			ldap_list_smbacct($user,"user");
		} else {
			ldap_list_user($user,"user");
		}
	} elsif ( $no_user eq "") {
		debug(0,"no users found with samba account found in database\n");
		exit 1;
	} else {
		debug(0,"no user $user with samba account in database\n");
		exit 1;
	}
};


# list workstations
if ( $list && defined($workstation) ) {

	my $no_workstation = $workstation;
	$workstation = $workstation || "*";
	my $USER_FILTER="(&(objectclass=$SMB_OC)(uid=$workstation))";
	
	if ( ldap_search($SUFFIX_MACHINES,$USER_FILTER) ) {
		debug(5,"list samba workstations: $workstation\n");
		ldap_list_smbacct($workstation,"workstation");
	} elsif ( $no_workstation eq "") { 
		debug(0,"no workstations found in database\n");
		exit 1;
	} else {
		debug(0,"no workstation $workstation with samba account in database\n");
		exit 1;
	}
};


# list groups 
if ( $list && defined($group) ) {

	my $nogroup = $group;
	$group = $group || "*";
	my $GROUP_FILTER = "(&(objectclass=posixGroup)(cn=$group))";

	if ( ldap_search($SUFFIX_GROUPS,$GROUP_FILTER) ) {
		debug(5,"list members of group: $group\n");
		ldap_list_group( $group );
	} elsif ( $nogroup eq "") {
		debug(0,"no groups found in database\n");
		exit 1;
	} else {
		debug(0,"no group $group in database\n");
		exit 1;
	}
}


# add workstation
if ( $add && $workstation ) {
	my $USER_FILTER="(&(objectclass=posixAccount)(uid=$workstation))";
	if ( ldap_search($SUFFIX_MACHINES,$USER_FILTER) ) {
		if ( $smbacct ) {
			debug(3,"adding machine-account: $workstation\n");
			ldap_smbwks_add($workstation);
			exit 0;
		}
		debug(0,"machine $workstation already exists. exiting.\n");
		exit 1;
	} else {
		debug(0,"adding machine: $workstation\n");
		if ($USEMAXID && !$uid) { 
			ldap_id("uid"); 
		} elsif ( !$uid ) {
			debug(0,"please define uidNumber with --uid=uidNumber\n");
			exit 1;
		} elsif ( $uid ) {
			$uidNumber = $uid;
		}
		ldap_posixwks_add($workstation);
		if ( $smbacct ) {
			debug(0,"adding machine-account: $workstation\n");
			ldap_smbwks_add($workstation);
		} 
	}
};

# delete user or workstation
if ( $delete && ($user||$workstation) ) {

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

	my $filter = "(&(objectclass=posixAccount)(uid=$name))";
	if ( ldap_search($suffix,$filter) ) {
		debug(0,"deleting $type: $name\n");
		ldap_delete_internal($name,$type,$entry->dn);
	} else {
		debug(0,"no such $type $name to delete\n");
		exit 1;
	}
};

# add group
if ( $add && $group ) {
	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	if ( ldap_search($SUFFIX_GROUPS,$GROUP_FILTER) ) {
		my $gidnumber = $entry->get_value( 'gidNumber');
		if ( $smbacct and $SAMBA_30 ) {
			debug(0,"adding ntgroup-mapping for unix-group: $group\n");
			ldap_ntgroup_add($group,$gidnumber);
			exit 0;
		}
		debug(0,"group $group already exists. exiting.\n");
		exit 1;
	} else {
		if ($USEMAXID && !$gid) { 
			ldap_id("gid"); 
		} elsif ( !$gid ) {
			debug(0,"please define gidNumber with --gid=gidNumber\n");
			exit 1;
		} elsif ( $gid ) {
			$gidNumber = $gid;
		}
		debug(0,"adding group: $group (with gidNumber: $gidNumber)\n");
		ldap_posixgroup_add($group);
		if ( $smbacct and $SAMBA_30 ) {
			debug(0,"adding ntgroup: $group\n");
			ldap_ntgroup_add($group);
		}
	}
};


# delete groupmapping
if ( $delete && $group && $smbacct && $SAMBA_30 ) {
	ldap_ntgroup_delete($group);
}


# delete group
if ( $delete && $group && !$smbacct ) {
	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	if ( ldap_search($SUFFIX_GROUPS,$GROUP_FILTER) ) {
		debug(0,"deleting group: $group\n");
		ldap_delete_internal($group,"group",$entry->dn);
	} else {
		debug(0,"no such group $group to delete. exiting.\n");
		exit 1;
	}
};


# add user to group
if ( $join && $user && $group ) {
	my $USER_FILTER="(&(objectclass=posixAccount)(uid=$user))";
	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	# check if user is there
	if ( ldap_search($SUFFIX_USERS,$USER_FILTER) ) {
		debug(5,"ok. user $user exists in ldap.\n");
	} elsif ( 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");
		exit 1;
	}
	# check if group is there
	if ( ldap_search($SUFFIX_GROUPS,$GROUP_FILTER) ) {
		debug(5,"ok. group $group exists\n");
		debug(0,"adding user $user to $group\n");
		ldap_user_to_group_add($user,$group);
	} else {
		debug(0,"no such group $group. exiting.\n");
		exit 1;
	}
};


# remove user from group
if ( $remove && $user && $group ) {
	my $USER_FILTER="(&(objectclass=posixAccount)(uid=$user))";
	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	# check if user is there
	if ( ldap_search($SUFFIX_USERS,$USER_FILTER) ) {
		debug(5,"ok. user $user exists in ldap.\n");
	} elsif ( getpwnam($user) ) {
		debug(5,"ok. user $user exists somwhere else in the name service switch.\n");
	} else {
		debug(0,"no such user $user. exiting.\n");
		exit 1;
	}
	# check if group is there
	if ( ldap_search($SUFFIX_GROUPS,$GROUP_FILTER) ) {
		debug(5,"ok. group $group exists\n");
		debug(0,"removing user $user from $group\n");
		ldap_user_from_group_remove($user,$group);
	} else {
		debug(0,"no such group $group. exiting.\n");
		exit 1;
	}
}


# init
if ( $init ) {
	ldap_init();
}


### whatever we have done, we're finished
ldap_unbind();
exit 0;





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


sub ldap_list_smbacct {

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

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

	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 ( $raw ) {

			$entry->dump;
			next;

		} elsif ( $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" );
	
			#need to handle 3_0 nua-accounts as well!
=cut
Munged dial:          
Logon time:           0
Logoff time:          Fri, 13 Dec 1901 21:45:51 GMT
Kickoff time:         Fri, 13 Dec 1901 21:45:51 GMT
Password last set:    Fri, 02 May 2003 17:31:58 GMT
Password can change:  0
Password must change: Fri, 13 Dec 1901 21:45:51 GMT
=cut

			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";
	
		}
	}
}


sub ldap_list_group {

	my $group = shift || "*";

	$mesg = $ldap->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 ( $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
			print "Groupmembers:\t@memberUid\n" if (@memberUid);
			print "Description:\t$description\n" if ($description);
			print "\n";
			print "--------------------------------\n" if ($mesg->entries > 1);
		} elsif ( $raw ) {
			$entry->dump;
		} else {
			print "$cn:x:$gid:@memberUid\n";
		}
	}
}


sub ldap_list_user {

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

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

	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 ( $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 ( $raw ) {
			$entry->dump;
		} else {
			print "$cn:x:$uid:\n";
		}
	}
}


sub ldap_posixuser_add {

	my ($homeDirectory,$userPassword,$cn);

	$homeDirectory	= $homedir 	|| "$HOMEDIR_ROOT/$ascii_user";
	$loginShell 	= $loginShell 	|| "/bin/bash";
	$cn 		= $comment	|| $user;

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

	# some more defaults
	my $uid		=	$user;
	my $gidNumber	=	$PRIMARYGIDNUMBER;
	my $RDN		=	"uid=$user";
	my $dn		=	"$RDN,$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' => $homeDirectory,
			'loginShell'    => $loginShell,
#			'gecos'         => $gecos, 
	);
	$entry->add (	'userPassword'  => "$userPassword" ) if ( $userPassword );

	if ( ! ldap_update("critical","adding user") ) {
		debug(5,"added user $uid posixAccount.\n");
	}

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


sub ldap_smbuser_add {

	my $uid = shift;	

	# create sambaAccount of user
	if ( $smbacct && $LOCAL_MODE ) {

		$passwd = $passwd || "";
		debug(5,"Creating samba account of user <$uid>\n");
		if ($DEBUG_PASSWORD) {
			debugadd(5,"with password <$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 
		$gecos = (getpwnam($uid))[6];
		my $ascii_displayName = umlaut2ascii($gecos);
	
		if ( $SAMBA_22 ) {
			my $pdbedit_opts = "-a -u -b -f '$ascii_displayName'";
			system( "echo -e \"$passwd\\n$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( "echo -e \"$passwd\\n$passwd\" | $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");
			exit 1;
		}
	}
}




sub ldap_usermodify {

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

	if ( $homedir ) {
		$changes{'homeDirectory'} = $homedir;
		$sambachanges{'-h'} = "";
	}
	if ( $gid ) {
		$changes{'gidNumber'} = $gid;
		$sambachanges{'-g'} = "";
	}
	if ( $uid ) {
		$changes{'uidNumber'} = $uid;
		$sambachanges{'-u'} = "";
	}
	if ( $loginShell ) {
		$changes{'loginShell'} = $loginShell;
	}
	if ( $passwd ) {
		my $salt =  pack("C2",(int(rand 26)+65),(int(rand 26)+65));
		$changes{'userPassword'} = crypt "$passwd",$salt;
		$changes{'userPassword'} = "{crypt}".$changes{'userPassword'};
	}
	if ( $comment ) {
		$changes{'cn'} = $comment;
		$sambachanges{'-f'} = "\"$comment\"";
	}
	my $dn = $entry->dn;

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

	# apply sambaAccount changes
	if ( $smbacct && $LOCAL_MODE ) {
		debug( 5,"modifying samba account of user <$user>.\n");
		if ( $passwd ) {
			if ($DEBUG_PASSWORD) {
				debugadd(5,"with password $passwd.\n");
			}
			system( "echo -e \"$passwd\\n$passwd\" | $progs{smbpasswd} -s $user --debug 0 2>&1 >/dev/null");
		}
		my ($opt,$arg);
		my $pdbeditopts = "-u \"$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 $user failed.\n");
		}
	}
}


sub ldap_posixwks_add {

	my $workstation = shift;

	my $homeDirectory	=	"/dev/null";
	my $loginShell		=	"/bin/false";
	my $uid			=	$workstation;
	my $gidNumber		=	$PRIMARYGIDNUMBER;
	my $RDN			=	"uid=$workstation";
	my $dn			=	"$RDN,$SUFFIX_MACHINES";
	my $cn 			= 	$comment || "Windows Workstation ".uc($workstation);

	# 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'     => $wksGidNumber,
			'homeDirectory' => $homeDirectory,
			'loginShell'    => $loginShell,
#			'gecos'         => $gecos,
	);

	# update
	if ( ! ldap_update("critical","adding machine account") ) {
		debug(5,"added machine account $uid\n");
	}

}


sub ldap_smbwks_add {

	my $wks = shift;

	# create sambaAccount of machine
	if ( $smbacct && $LOCAL_MODE ) {
		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");
		}
	}

}

sub ldap_delete_internal ($$$) {

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

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

sub ldap_posixgroup_add {

	my $group	= shift;
	my $cn		= "$group";
	my $RDN		= "cn=$cn";
	my $dn 		= "$RDN,$SUFFIX_GROUPS";

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

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

	# update
	if ( ! ldap_update("critical","adding posixgroup") ) {
		debug(10,"added posix group $group\n");
	}

}


sub ldap_ntgroup_add {

	my $ntgroup = shift;
	my $rid = shift || "$gidNumber";
	my $comment = "No comment";
	my $ntgroup_type = "domain";
	my $ret = system("$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("$net groupmap delete ntgroup=$ntgroup \
		-d 0 2>&1 >/dev/null");
	return $ret;

}


sub ldap_user_to_group_add {

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

	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	my $mesg = ldap_search("$SUFFIX_GROUPS","$GROUP_FILTER");

	my @members = $entry->get_value( 'memberUid' );
	debug(5,"group $group has currently these members: @members\n");
	if ( "@members" =~ /$user/i ) { 
		debug(3,"already member. doing nothing...\n");
		exit 0;
	} else {
		push (@members,$user);
		$entry->add( 'memberUid' => "$user");	
		debug(5,"group $group now has these members: @members\n");

		# update
		if ( ! ldap_update("critical","adding user to posixgroup") ) {
			debug(10,"added user to posix group $group\n");
		}
	}
}


sub ldap_user_from_group_remove {

	my $GROUP_FILTER="(&(objectclass=posixGroup)(cn=$group))";
	my $mesg = ldap_search("$SUFFIX_GROUPS","$GROUP_FILTER");
	my @members = $entry->get_value( 'memberUid' );
	debug(5,"group $group currently has these members: @members\n");
	if ( "@members" =~ /$user/i ) {
		# FIXME
		debug(5,"group $group now has these members: @members\n");
		$entry->delete( 'memberUid' => ["$user"]);

		# update
		if ( ! ldap_update("critical","removed user from posixgroup") ) {
			debug(10,"removed user from posix group $group\n");
		}

	} else {
		debug(3,"doing nothing. $user is not a member of $group\n");
		exit 0;
	}
}


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

sub ldap_search {

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

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

	# construct the query
	my $mesg = $ldap->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 {
		my $max = $mesg->count;
		debugadd(5,"found $max matching entrie(s)\n");
		for (my $i = 0 ; $i < $max ; $i++) {
			my $show_entry = $mesg->entry($i);
			my $dn = $show_entry->dn;
			debugadd(5,"found entry with dn:\t\"$dn\"\n");
		}
		debugadd(5,"################ finished LDAP query ################\n");
		return 1;
	}
}


sub ldap_bind {

	my $mode = shift || "qualified";

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

	my $mesg;
	my $status = "";
	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,"3"); 
		die;
	} else {
		debugadd(10,"$status was successful.\n");
	}
} 


sub ldap_unbind {

	my $status = "unbinding";
	$mesg = $ldap->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 {

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

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

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

	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 1;
		}

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


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

sub ldap_id {

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

	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");
		exit 1;
	}

	# another subfunction
	sub check_maxid ($$) {

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

	sub check_used_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 1;
			}
		} elsif ($id_type eq "group") {
			if (getgrgid($id)) {
				debug(0,"gid $id is already in use\n");
				return 1;
			}
		}
		return 0;
	}

	debug(5,"got order to generate a new $ldap_attr\n");
	my ($MIN_ID,$MAX_ID) = split(/-/,$ID_RANGE);

	my $INIT_MAXID = "$MIN_ID"; 

	# look for maxid in ldap
	if (ldap_search($SUFFIX,"(&(objectclass=top)(uid=$ldap_uid_attr))")) {

		# if it is there, get the value
		my $LDAP_MAXID = $entry->get_value( 'description' ); 

		# check maxid
		check_maxid($LDAP_MAXID,$MAX_ID);

		# 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($SUFFIX,"(&(objectclass=$oc)($ldap_attr=$NEW_MAXID))") ||
			     check_used_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
				check_maxid($LDAP_MAXID,$MAX_ID);

			} else {

				# check maxid
				check_maxid($LDAP_MAXID,$MAX_ID);

				# 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("critical","updating maxid ($id)");
				debug(5,"updated MAXID ($id)\n");

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

		return 0;

	} 
	
	# 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($SUFFIX,"(&(objectclass=$oc)($ldap_attr=$INIT_MAXID))") ||
		     check_used_id($INIT_MAXID,$user_or_group)) {

			# check maxid
			check_maxid($INIT_MAXID,$MAX_ID);

			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
			check_maxid($INIT_MAXID,$MAX_ID);

			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("critical","adding maxid for $id");
			debugadd(5,"updated MAXID for $id\n");

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

			$found_id = 1;
		}

	}
	return 0;

} 



sub ldap_init {

	# FIXME: this is rubbish...
	debug(3,"initializing your ldap-tree, some data will be deleted!\n");
	my @ous = ($SUFFIX, $SUFFIX_GROUPS, $SUFFIX_USERS, $SUFFIX_MACHINES, $SUFFIX_IDMAP);
	my $result;
	foreach my $ou (@ous) {
		my $tmp = $ou;
		$tmp =~ s/^ou=//i;
		$tmp =~ s/,.*//i;
		debug(3,"adding: \t $ou\n");
		$result = $ldap->add ( 	"$ou",
			attrs => [ 'objectClass' => 'organizationalUnit', 'ou' => "$tmp" ]);

	}

}


sub print_smb_conf {

	# print what we got
	print "\nRunning-mode:\t\t\t$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 ( $USEMAXID ) {
		print "\nMAXID-settings stored in ldap:\n";
		if ( ldap_search("$SUFFIX","(&(objectclass=top)(uid=maxuid))") ) {
			my $MAXUID = $entry->get_value( 'description' ); 
			print "\tmaxUid:\t\t\t$MAXUID\n";
			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("$SUFFIX","(&(objectclass=top)(uid=maxgid))") ) {
			my $MAXGID = $entry->get_value( 'description' ); 
			print "\tmaxGid:\t\t\t$MAXGID\n";
			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 {

	my $tmp = shift;
	my $msg = shift;
	my $add = shift;

	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 >= $tmp ) {
	
		# write to STDOUT and with a timestamp to our logfile
		select(LOGFILE_HANDLE);
		$~ = ($add?"LOGFILE_HANDLE_ADD":"LOGFILE_HANDLE");
		write(LOGFILE_HANDLE);
		select(STDOUT);
		my $ofh = select(STDOUT);
		$~ = "DEBUG_STDOUT";
		select($ofh);
		write();
	} elsif ( $tmp <= 10 ) {
		# write to STDOUT and with a timestamp to our logfile
		select(LOGFILE_HANDLE);
		$~ = ($add?"LOGFILE_HANDLE_ADD":"LOGFILE_HANDLE");
		write(LOGFILE_HANDLE);
		select(STDOUT);
	}
	
format LOGFILE_HANDLE_ADD =
   @*
$msg
.
format LOGFILE_HANDLE =
[@<<<<<<<<<<<<<<] @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< 
$date, $prog
   @*
$msg
.
format DEBUG_STDOUT =
@<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @*
$prog, $msg
.
}

sub debugadd {

	my $tmp = shift;
	my $msg = shift;
	debug($tmp,$msg,"1");

}

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 ;

	# 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 ;

	return $name;

}


sub init_logfile {

	# open the logfile, or create it if not there
	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-2004 Guenther Deschner, Lars Mueller\n");

}


sub close_logfile {

	close (LOGFILE_HANDLE) || die "could not close file: $!";

}


# parse the smb.conf
sub parse_smbconf {

	# TODO: read testparm, smb.conf and .ldapsmbrc (in reverse order) after another 
	# and then work through the list.

	my $handle;

	# extra conf file?
	if ($smbconfig) {
		debug(3,"using $SMB_CONF\n");
		$handle = "< $SMB_CONF";
	} else {
		debug(3,"using testparm\n");
		$handle = "$progs{testparm} -s 2> /dev/null |";
	}

	# open handle and read
	open (DATA, "$handle") || die "could not open $SMB_CONF: $!";
	while ($line = <DATA>) {
		next if $line =~ /^#/;
		chomp($line);
		if ($line =~ /ldap admin dn/ && $ADMIN_DN eq "") {
			($dummy,$ADMIN_DN) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap admin dn\":\t\t$ADMIN_DN\n");
		}
		if ($line =~ /ldap suffix/ && $SUFFIX eq "") {
			($dummy,$SUFFIX) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap suffix\":\t\t$SUFFIX\n");
		}
		if ($line =~ /ldap machine suffix/ && $SUFFIX_MACHINES eq "") {
			($dummy,$SUFFIX_MACHINES) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap machine suffix\":\t$SUFFIX_MACHINES\n");
		}
		if ($line =~ /ldap user suffix/ && $SUFFIX_USERS eq "") {
			($dummy,$SUFFIX_USERS) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap user suffix\":\t$SUFFIX_USERS\n");
		}
		if ($line =~ /ldap group suffix/ && $SUFFIX_GROUPS eq "") {
			($dummy,$SUFFIX_GROUPS) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap group suffix\":\t$SUFFIX_GROUPS\n");
		}
		if ($line =~ /ldap idmap suffix/ && $SUFFIX_IDMAP eq "") {
			($dummy,$SUFFIX_IDMAP) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap idmap suffix\":\t$SUFFIX_IDMAP\n");
		}
		if ($line =~ /ldap ssl/ && $LDAP_SSL eq "") {
			($dummy,$LDAP_SSL) = split(/.*=\s+/,$line);
			debugadd(10,"autodetected \"ldap ssl\":\t$LDAP_SSL\n");
		}
		# the following two options are only in 2.2.x
		if ($SAMBA_22) {
			if ($line =~ /ldap server/ && $LDAP_SERVER eq "") {
				($dummy,$LDAP_SERVER) = split(/.*=\s+/,$line);
				debugadd(10,"autodetected \"ldap server\":\t\t$LDAP_SERVER\n");
			}
			if ($line =~ /ldap port/ && $LDAP_PORT eq "") {
				($dummy,$LDAP_PORT) = split(/.*=\s+/,$line);
				debugadd(10,"autodetected \"ldap port\":\t\t$LDAP_PORT\n");
			}
		}
		# the following option is in HEAD/3.0 only
		# FIXME: handle more ldapsam-backends
		# FIXME: check ssl!
		# FIXME: accept predefined variables (see above)
		if ($SAMBA_30) {
			if ($line =~ /passdb backend/) {
				($dummy,$passdb) = split(/.*=\s+/,$line);
				$passdb =~ s/^.*ldapsam:ldap:\/\///g;
				$passdb =~ s/(, | ).*//g;
				($LDAP_SERVER,$LDAP_PORT) = split(/:/,$passdb);
				$LDAP_PORT = $LDAP_PORT  || "389";
				debugadd(10,"autodetected \"ldap server\":\t\t\"$LDAP_SERVER\"\n");
				debugadd(10,"autodetected \"ldap port\":\t\t\"$LDAP_PORT\"\n");
			} 
		}
	}
	close(DATA);

	if (!$passdb) {
		debug(3,"empty \"passdb\" in $SMB_CONF. using defaults for LDAP_SERVER and LDAP_PORT\n");
	}

	if (!$SUFFIX) {
		debug(0,"no suffix defined. exiting.\n");
		exit(1);
	}

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

	# check if $SUFFIX_MACHINES is set and try to read from config file
	if ( ( ! $SUFFIX_MACHINES || ($SUFFIX_MACHINES eq "") ) && -e $LDAPSMBRC ) {
		open (DATA, "< $LDAPSMBRC");
		if ($?) {
			debug(0,"can't open config file ".$LDAPSMBRC."\n");
			exit 1;
		}
		while ($line = <DATA>) {
			next if $line =~ /^#/;
			if ($line =~ /ldap machine suffix/) {
				($dummy,$SUFFIX_MACHINES) = split(/.*=\s+/,$line);
				chomp($SUFFIX_MACHINES);
				debug(10," config file \"ldap machine suffix\":\t$SUFFIX_MACHINES\n");
			}
		}
		close(DATA);

		if ( !($SUFFIX_MACHINES) || ($SUFFIX_MACHINES eq "")) {
			$SUFFIX_MACHINES = $SUFFIX;
			debug(10,"no \"ldap machine suffix\" detected. Using value of \"ldap suffix\" instead.\n");
		}
	}

	# check if $SUFFIX_USERS is set and try to read from config file
	if ( ( ! $SUFFIX_USERS || ($SUFFIX_USERS eq "") ) && -e $LDAPSMBRC ) {
		open (DATA, "< $LDAPSMBRC");
		if ($?) {
			debug(0,"can't open config file ".$LDAPSMBRC."\n");
			exit 1;
		}
		while ($line = <DATA>) {
			next if $line =~ /^#/;
			if ($line =~ /ldap user suffix/) {
				($dummy,$SUFFIX_USERS) = split(/.*=\s+/,$line);
				chomp($SUFFIX_USERS);
				debug(10," config file \"ldap user suffix\":\t$SUFFIX_USERS\n");
			}
		}
		close(DATA);

	}

	if (! $SUFFIX_USERS || ($SUFFIX_USERS eq "")) {
		$SUFFIX_USERS = $SUFFIX;
		debugadd(10,"no \"ldap user suffix\" detected. Using value of \"ldap suffix\" instead.\n");
	}
	if (! $SUFFIX_GROUPS || ($SUFFIX_GROUPS eq "")) {
		$SUFFIX_GROUPS = $SUFFIX;
		debugadd(10,"no \"ldap group suffix\" detected. Using value of \"ldap suffix\" instead.\n");
	}
	if (! $SUFFIX_MACHINES || ($SUFFIX_MACHINES eq "")) {
		$SUFFIX_MACHINES = $SUFFIX;
		debugadd(10,"no \"ldap machine suffix\" detected. Using value of \"ldap suffix\" instead.\n");
	}

	# 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);
}

sub check_suffix {
	
	my $suffix = shift;
	my $ret = $suffix;

	if ( $suffix !~ /$SUFFIX/i ) {
		$ret = sprintf("%s,%s", $suffix,$SUFFIX);
	}
	return $ret;

}


sub find_adminpw {

	# why don't we just read with tdbedit ?

	my $key_pattern;
	my $match_pattern;
	my $subst_pattern;
	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;
	if ( $LOCAL_MODE && (-e $confs{'secrets.tdb'} ) ) {
		open (DATA, "$progs{tdbdump} $confs{'secrets.tdb'} 2> /dev/null|") || die "could not open $confs{'secrets.tdb'}: $!";
		while ($line = <DATA>) {
			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;
				} 
			} elsif ($cnt == 1) {
				($dummy,$TDB_PW) = split(/\"/,$line);
				$TDB_PW =~ s/\\.*$//g;
				if ($DEBUG_PASSWORD) {
					debug(10,"autodetected \"ldap admin password\":\t$TDB_PW\n");
				} else {
					debug(10,"autodetected \"ldap admin password\":\tnot logged...\n");
				}
				last;
			} 
		}
		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");
		exit 1;
	}
	return $TDB_PW;
}


sub find_progs (@) {

	my @progs = @_;

	my @paths = split(/:/, $ENV{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 1;
		}
	}
}

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 1;
		}
	}
}


sub get_smbd_samba_ver ($) {

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

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


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 $schema = $ldap->schema();
	if ( $schema->attribute( 'sambaSID' ) ) {
		return "3_0"; 
	} elsif ( $schema->attribute( 'rid' ) ) {
		return "2_2";
	} else {
		return "unknown samba-version";
	}

}

sub detect_mode {

	if ( $MODE =~ /local/i ) {
		$LOCAL_MODE = 1;
	} elsif ( $MODE =~ /remote/i ) {
		$REMOTE_MODE = 1;
	} else {
		debug(0,"unknown mode choosen. exiting\n");
		exit 1;
	}

}

sub prepare_account {

	# handle umlauts (unless utf8 is widly adopted...)

	if ($user) {
		if (! $force && $add ) {
			my $ret = check_sane_name($user);
			if ($ret) { 
				debug(0,"your username ($user) has strange characters that might cause problems:\n");
				debug(0,"\t$ret\n");
				debug(0,"you can force account creation with -f\n");
				exit 1;
			}
		}

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

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

sub get_gid ($) {

	# handle gid
	my $gid = shift;
	if ( $gid && !( $gid =~ /^[0-9]/ ) ) {
		debug(5,"gid $gid is not numeric. converting\n");
		if ( ldap_search($SUFFIX,"(&(objectclass=posixGroup)(cn=$gid))") ) {
			$gid = $entry->get_value('gidNumber');
		} elsif ($gid = getgrnam($gid)) {
		} else {
			debug(0,"could not resolve group: $gid to numeric gid\n");
			exit 1;
		}
	}
	return $gid;
}

__END__

=head1 NAME

B<ldapsmb> - LDAP-Managment-Tool for a Samba Domain Controller

=head1 SYNOPSIS

ldapsmb [options] 

	Main Options:
--add|-a				Add something
--config				Show configuration
--delete|-d				Delete something
--group|-g <groupname>			Set Group-Name
--help|-h|?				Display help
--init|-i				Initialize LDAP 
--join|-j				Join a user to a group
--list|-l				List Something
--modify|-m				Modify something
--remove|-r				Remove a user from a group
--smbacct|-s				Promote to samba-Account
--user|-u <username>			Set User-Name
--workstation|-wks <workstationname>	Set Workstation-Name

	Global Options:
--debug <n>				Select debug-level (default: 3)
--force|-f				Force execution
--mode					Select mode (default: local)
--quiet|-q				No output
--raw					Raw list-output
--smbconf <smb.conf>			Choose another configfile
--verbose|-v				Verbose output
--version				Display version

	Misc Options:
--comment|-c <comment>			Select comment
--gid <n>				Set Gid-Number
--homedir <home directory>		Set Home-Directory
--makehomedir				Make Home-Directory
--ntgroup <ntgroupname>			Set NT-Groupname
--passwd <password>			Set password 
--shell <login shell>			Select shell
--skeldir|-k <skeleton dir>		Define Skeleton-Dir
--uid <n>				Set Uid-Number

=head1 DESCRIPTION

B<ldapsmb> will create and delete Posix-Accounts for users, groups and
workstations in your LDAP-Directory. Although designed for Samba 3.0/HEAD it
should work for Samba 2.2.x as well. Furthermore B<ldapsmb> should provide all
necessary scripting-hooks to fullfill a clean "net rpc vampire" - Migration of a
NT4/2000 Domain Controller to a Samba 3.0 PDC. 

B<ldapsmb> can run in two modes:

	local:	your smbd is running on the same machine where B<ldapsmb> is called.
	remote:	your smbd is running on another machine.

All LDAP relevant configuration data will be autodetected if possible. Your
password can be autodetected as well, as long as you have read permission on
your secrets.tdb where your admin password will be stored after you have called
B<smbpasswd -w adminpassword>. If you have not done that (e. g. running in
remote mode) you have to set the password manually in B<ldapsmb>.

The file C<~/.ldapsmbrc> could be used to set additional configuration
parameters not yet part of the smb.conf.

=head1 MAIN OPTIONS

=over 8

=item B<--add|-a>

Add an account. Requires B<--user|-u>, B<--group|-g> or B<--workstation|-wks>. Can be
combined with B<-smbacct> do promote the posixAccount to a full sambaAccount if
running in local-mode.

=item B<--config>

Show the config that will be used (the result of all autodetections).

=item B<--delete|-d>

Delete an account. Requires B<--user|-u>, B<--group|-g> or
B<--workstation|-wks>.

=item B<--group|-g> C<groupname>

Define a groupname. Requires B<-add>, B<-delete>, B<-list>, B<-join> or
B<-remove>.

=item B<--help|-h>

Print a brief help message and exits.

=item B<--init|-i>

to be documented...

=item B<--join|-j>

Join a LDAP-PosixAccount to a LDAP-PosixGroup. Requires B<--user|-u> and
B<--group|-g>. A corresponding memberUid-attribute with the given username will
be added to the posixGroup-object.

=item B<--list|-l>

List an account. Requires B<--user|-u>, B<--group|-g> or B<--workstation|-wks>.
If no user, group or workstation is specified, all entries of the specific
account are listed.

=item B<--mode> C<mode>

Choose in which mode B<ldapsmb> should run (local, remote). If running in local
mode (the default) it will check for ldap-support in smbd, look for a
secrets-tdb to retrieve the password, etc. If running in remote-mode, you need
to assign a config-file with --smbconf /path/to_my/smb.conf and you need to
write your admin-password into B<ldapsmb>. Adding or promoting Accounts to full
LDAP-Accounts is currently only available in local mode.

=item B<--modify|-m>

Modify an account. Requires B<--user|-u>, B<--group|-g> or
B<--workstation|-wks>.

=item B<--raw>

Raw output while listing.

=item B<--smbacct>

Trigger all necessary steps to make either a user, a group or a workstation a
full Samba-Account.  This option will be redesigned in the future. It was added
by Lars M�ller to ease Samba 2.2-Administration but really makes not very much
sense for Samba 3.0. By default samba-specific information are not added.

=item B<--remove|-r>

Remove a LDAP-PosixAccount from a LDAP-PosixGroup. Requires B<-user> and
B<-group>. If existant, the memberUid-attribute for a given username will be
removed from the posixGroup-object.

=item B<--user|-u> C<username>

Define a username. Requires B<-add>, B<-delete>, B<-list>, B<-join> or
B<-remove>.

=item B<--workstation|-wks> C<workstation>

Define a workstation. Requires B<-add>, B<-delete>, B<-list>, B<-join> or
B<-remove>.

=head1 GLOBAL OPTIONS

=item B<--debug n>

Use an debuglevel. You can choose between 3 (default), 5 and 10 (full
debugging).

=item B<--force|-f> 

Force execution.

=item B<--quiet|-q>

No output.

=item B<--smbconf> F<smb.conf>

Use another smb.conf-file instead of the default location. Needed for running
in remote mode.

=item B<--verbose|-v>

Prints additional information if possible.

=item B<--version>

Prints the version and exits.

=head1 MISC OPTIONS

=item B<--comment|-c> C<comment>

Set a comment for a given user while adding or modifying a user.

=item B<--homedir> C<homedir>

Set the user's login directory while adding or modifying a user. The default
is to append the login name to default_home and use that as the login directory
name.

=item B<--makehomedir>

Create user's home directory while adding a user. The files contained in
/etc/skeleton will be copied to the home directory.

=item B<--passwd> C<pwd>

Set a password for a given user while adding or modifying a user. If no
password is set, the password is identical with the userid (FIXME).

=item B<--shell> C<shell>

Set the login shell while adding or modifying a user. The default is to leave
this field blank, which causes the system to select the default login shell.

=item B<--skeldir skeldir>

Set a different skeleton directory while using the B<-makehomedir> option.

=back

=head1 EXAMPLES

C<ldapsmb -a -u gd -passwd 'secret' -smbacct> 

This will create a user called gd in LDAP and will add all required information
to make that a full samba-Account.

C<ldapsmb -j -u lmuelle -g admins> 

This will add the user lmuelle to the admins-group in ldap.

=head1 AUTHOR

G�nther Deschner <gd@suse.de>, Lars Mueller <lmuelle@suse.de>.

If you find any errors in the code please let me know at gd@suse.de.

=head1 BUGS

B<ldapsmb> is not yet fully UNICODE-aware. B<ldapsmb> does not yet deal with
idmap in ldap. Both features will be added in a future release.

=head1 VERSION

This is version 1.31 of B<ldapsmb>.

=head1 COPYRIGHT

Copyright (c) 2003-2004 SuSE Linux AG. All rights reserved.

This program is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

=cut
