#!/usr/local/bin/perl
# save_user.cgi
# Create, update or delete an LDAP user

require './ldap-useradmin-lib.pl';
use Time::Local;
&ReadParse();
$ldap = &ldap_connect();
$schema = $ldap->schema();
if (!$in{'new'}) {
	# Get existing user
	$rv = $ldap->search(base => $in{'dn'},
			    scope => 'base',
			    filter => '(&(objectClass=posixAccount))');
	($uinfo) = $rv->all_entries;
	$uinfo || &error($text{'usave_egone'});
	%ouser = &dn_to_hash($uinfo);
	}

if ($in{'delete'}) {
	# Delete the user .. but ask first!
	&ui_print_header(undef, $text{'udel_title'}, "");
	$home = $uinfo->get_value("homeDirectory");
	$user = $uinfo->get_value("uid");
	if ($in{'confirm'}) {
		# Delete from other modules
		%user = &dn_to_hash($uinfo);
		if ($in{'others'}) {
			print "$text{'udel_other'}<br>\n";
			&useradmin::other_modules("useradmin_delete_user",
						  \%user);
			print "$text{'udel_done'}<p>\n";
			}

		# Delete from any groups
		print "$text{'udel_groups'}<br>\n";
		$base = &get_group_base();
		$rv = $ldap->search(base => $base,
				    filter => '(&(objectClass=posixGroup))');
		foreach $g ($rv->all_entries) {
			local @mems = $g->get_value("memberUid");
			local $idx = &indexof($user, @mems);
			if ($idx >= 0) {
				# Take out of this group
				splice(@mems, $idx, 1);
				$rv = $ldap->modify($g->dn(), replace =>
					{ 'memberUid' => \@mems });
				if ($rv->code) {
					&error(&text('usave_emodgroup',
						     $g->get_value('cn'),
						     $rv->error));
					}
				}
			}
		print "$text{'udel_done'}<p>\n";

		# Delete from the LDAP db
		print "$text{'udel_pass'}<br>\n";
		$rv = $ldap->delete($in{'dn'});
		if ($rv->code) {
			&error(&text('usave_edelete', $rv->error));
			}
		print "$text{'udel_done'}<p>\n";

		# Delete his addressbook entry
		if ($config{'addressbook'}) {
			print "$text{'udel_book'}<br>\n";
			&delete_addressbook();
			print "$text{'udel_done'}<p>\n";
			}

		# Delete his home directory
		if ($in{'delhome'}) {
			print "$text{'udel_home'}<br>\n";
			$home = $uinfo->get_value("homeDirectory");
			if (-d $home && $home ne "/") {
				local $realhome = &resolve_links($home);
				local $qhome = quotemeta($realhome);
				system("rm -rf $qhome >/dev/null 2>&1");
				unlink($home);	# in case of links
				}
			print "$text{'udel_done'}<p>\n";
			}

		# Delete his IMAP mailbox
		if ($config{'imap_host'}) {
			print "$text{'udel_imap'}<br>\n";
			$imap = &imap_connect();
			$rv = $imap->delete("user.".$uinfo->get_value("uid"));
			$imap->logout();
			print "$text{'udel_done'}<p>\n";
			}

		%p = ( %in, %user );
		&webmin_log("delete", "user", $user{'user'}, \%p);
		}
	else {
		# Show confirmation page
		print "<form action=save_user.cgi>\n";
		print "<input type=hidden name=dn value=\"$in{'dn'}\">\n";
		print "<input type=hidden name=delete value=1>\n";
		print "<input type=hidden name=confirm value=1>\n";

		if ($home ne "/" && -d $home) {
			$size = &disk_usage_kb($home);
			print "<center><h3>",&text('udel_sure', $user, $home,
						   $size),"</h3>\n";
			print "<input type=submit ",
			      "value=\"$text{'udel_del1'}\">\n";
			print "<input name=delhome type=submit ",
			      "value=\"$text{'udel_del2'}\">\n";
			}
		else {
			print "<center><h3>",&text('udel_sure2', $user),
			      "</h3>\n";
			print "<input type=submit ",
			      "value=\"$text{'udel_del1'}\">\n";
			}
		if ($user eq 'root') {
			print "<h3><font color=#ff0000>",
			      "$text{'udel_root'}</font></h3>\n";
			}
		printf "<br><input type=checkbox name=others value=1 %s> %s<br>\n",
			$uconfig{'default_other'} ? "checked" : "",
	      		$text{'udel_dothers'};
		print "</form></center>\n";
		}

	$ldap->unbind();
	&ui_print_footer("", $text{'index_return'});
	exit;
	}
else {
	# Validate inputs
	&error_setup($text{'usave_err'});
	$in{'user'} =~ /^[^:\t]+$/ ||
		&error(&text('usave_ebadname', $in{'user'}));
	$user = $in{'user'};
	$in{'uid'} =~ /^\-?[0-9]+$/ || &error(&text('usave_euid', $in{'uid'}));
	$uid = $in{'uid'};
	$in{'real'} =~ /^[^:]*$/ || &error(&text('usave_ereal', $in{'real'}));
	$firstname = $in{'firstname'};
	$lastname = $in{'lastname'};
	$real = $in{'real'};
	$shell = $in{'shell'} eq '*' ? $in{'othersh'} : $in{'shell'};
	if ($in{'home_base'}) {
		$home = &useradmin::auto_home_dir($uconfig{'home_base'}, $user);
		}
	else {
		$home = $in{'home'};
		$home =~ /^\// || &error(&text('usave_ehome', $home));
		}
	local $pfx = $config{'md5'} == 1 || $config{'md5'} == 3 ? "{md5}" :
	       	     $config{'md5'} == 0 ? "{crypt}" : "";
	if ($in{'passmode'} == 0) {
		$pass = "";
		}
	elsif ($in{'passmode'} == 1) {
		$pass = $uconfig{'lock_string'};
		}
	elsif ($in{'passmode'} == 2) {
		$pass = $in{'encpass'};
		$pass = $pfx.$pass if ($pass !~ /^\{[a-z0-9]+\}/i && $pfx);
		}
	elsif ($in{'passmode'} == 3) {
		$pass = $pfx.&encrypt_password($in{'pass'});
		$plainpass = $in{'pass'};
		}
	$gid = getgrnam($in{'gid'});
	defined($gid) || &error(&text('usave_egid', $in{'gid'}));

	if ($in{'new'}) {
		defined(getpwnam($user)) && &error(&text('usave_einuse', $user));
		if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
			if ($in{'cyrus'}) {
				&error($text{'usave_ecyruspass'});
				}
			}

		# Create home dir
		if (!-e $home && $in{'makehome'}) {
			&lock_file($home);
			mkdir($home, oct($uconfig{'homedir_perms'})) ||
				&error(&text('usave_emkdir', $!));
			chmod(oct($uconfig{'homedir_perms'}), $home) ||
				&error(&text('usave_echmod', $!));
			chown($uid, $gid, $home) ||
				&error(&text('usave_echown', $!));
			&unlock_file($home);
			}

		# Get the properties for new users
		local %uhash = ( 'user' => $user,
				 'uid' => $uid,
				 'gid' => $gid,
				 'group' => $in{'group'},
				 'real' => $real,
				 'shell' => $shell,
				 'pass' => $pass,
				 'plainpass' => $plainpass,
				 'home' => $home );
		local @props = &split_props($config{'props'}, \%uhash);
		if ($in{'samba'}) {
			push(@props, &split_props($config{'samba_props'},
						  \%uhash));
			}
		if ($in{'cyrus'}) {
			push(@props, &split_props($config{'imap_props'},
						  \%uhash));
			}

		# Build Samba-related properties
		if ($in{'samba'}) {
			# Work out the Samba password
			local $xes = ("X" x 32);
			local ($nt, $lm);
			if ($in{'passmode'} == 0) {
				# No password needed
				$nt = "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX";
				$lm = $nt;
				$opts = "UN";
				}
			elsif ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
				# No login allowed, or pre-encrypted password
				# that we cannot support
				$nt = $xes;
				$lm = $xes;
				$opts = "UD";
				}
			elsif ($in{'passmode'} == 3) {
				# Plain-text password to convert
				($nt, $lm) = &samba_password($in{'pass'});
				$opts = "U";
				}
			&samba_password_props($nt, $lm);

			# Set other samba-related options
			push(@props, "ntuid", $user)
				if ($schema->attribute("ntuid") &&
				    $config{'samba_class'} eq 'sambaAccount');

			push(@props, "rid", $uid*2+1000)
				if ($schema->attribute("rid") &&
				    $config{'samba_class'} eq 'sambaAccount');
			push(@props, "sambaSID",
				     "$config{'samba_domain'}-".($uid*2+1000))
				if ($schema->attribute("sambaSID") &&
				    $config{'samba_class'} eq 'sambaSamAccount');


			#push(@props, "sambaPrimaryGroupSID",
			#	     $config{'samba_domain'}.'-'. ($gid*2+1000))
			#	if ($schema->attribute("sambaPrimaryGroupSID") &&
			#	    $config{'samba_class'} eq 'sambaSamAccount');

			push(@props, "acctFlags", sprintf("[%-11s]", $opts))
				if ($schema->attribute("acctFlags") &&
				    $config{'samba_class'} eq 'sambaAccount');

			push(@props, "sambaAcctFlags", sprintf("[%-11s]",$opts))
				if ($schema->attribute("sambaAcctFlags") &&
				    $config{'samba_class'} eq 'sambaSamAccount');
			}

		if ($in{'cyrus'}) {
			# Build mail-related properties
			&mail_props();
			}

		# Add any extra LDAP fields
		&extra_fields();

		# Add shadow LDAP fields
		$shadow = &shadow_fields();

		# Add to the ldap database
		@classes = ( "posixAccount", "shadowAccount" );

		push(@classes, split(' ',$config{'other_class'}));
		push(@classes, $samba_class) if ($in{'samba'});
		push(@classes, $cyrus_class) if ($in{'cyrus'});
		$base = &get_user_base();
		$newdn = "uid=$user,$base";
		&name_fields();
		$rv = $ldap->add($newdn, attr =>
                        [ "cn" => $real,
			  "uid" => $user,
			  "uidNumber" => $uid,
			  "loginShell" => $shell,
			  "homeDirectory" => $home,
			  "gidNumber" => $gid,
			  "userPassword" => $pass,
			  "objectClass" => \@classes,
			  @props ]);
		if ($rv->code) {
			&error(&text('usave_eadd', $rv->error));
			}

		if ($in{'cyrus'}) {
			if ($config{'addressbook'}) {
				# Create addressbook entry
				&setup_addressbook();
				}

			# Create imap account
			&setup_imap();
			}

		# Copy files into user's directory
		if ($in{'makehome'} && $uconfig{'user_files'}) {
			local $uf = $uconfig{'user_files'};
			local $shell = $user{'shell'}; $shell =~ s/^(.*)\///g;
			$uf =~ s/\$group/$in{'gid'}/g;
			$uf =~ s/\$gid/$user{'gid'}/g;
			$uf =~ s/\$shell/$shell/g;
			&useradmin::copy_skel_files($uf, $home, $uid, $gid);
			}
		}
	else {
		$olduser = $uinfo->get_value('uid');
		if ($olduser ne $user) {
			defined(getpwnam($user)) &&
				&error(&text('usave_einuse', $user));
			}

		# Work out old settings
		@classes = $uinfo->get_value("objectClass");
		$wassamba = &indexof($samba_class, @classes) >= 0;
		$wascyrus = &indexof($cyrus_class, @classes) >= 0;
		if ($in{'passmode'} == 1 || $in{'passmode'} == 2) {
			if (!$wascyrus && $in{'cyrus'}) {
				&error($text{'usave_ecyruspass'});
				}
			}

		# Rename home dir, if needed
		$oldhome = $uinfo->get_value("homeDirectory");
		if ($home ne $oldhome && -d $oldhome && !-e $home &&
		    $in{'movehome'}) {
			$out = `mv '$oldhome' '$home' 2>&1`;
			if ($?) { &error(&text('usave_emove', $out)); }
			}

		# Change GID on files if needed
		$oldgid = $uinfo->get_value("gidNumber");
		$olduid = $uinfo->get_value("uidNumber");
		if ($oldgid != $gid && $in{'chgid'}) {
			if ($in{'chgid'} == 1) {
				&useradmin::recursive_change($home, $olduid,
							     $oldgid, -1, $gid);
				}
			else {
				&useradmin::recursive_change("/", $olduid,
							     $oldgid, -1, $gid);
				}
			}

		# Change UID on files if needed
		if ($olduid != $uid && $in{'chuid'}) {
			if ($in{'chuid'} == 1) {
				&useradmin::recursive_change($home, $olduid,
							     -1, $uid, -1);
				}
			else {
				&useradmin::recursive_change("/", $olduid,
							     -1, $uid, -1);
				}
			}

		# Work out samba-related property changes
		$oldpass = $uinfo->get_value('userPassword');
		if ($in{'samba'}) {
			# Is a samba user .. add or update props
			local $xes = ("X" x 32);
			local ($nt, $lm);
			if ($in{'passmode'} == 0) {
				# No password needed
				$nt = "NO PASSWORDXXXXXXXXXXXXXXXXXXXXX";
				$lm = $nt;
				$opts = "UN";
				}
			elsif ($in{'passmode'} == 1) {
				# No login allowed
				if ($config{'samba_class'} eq
				    'sambaSamAccount') {
					$nt = "sambaNTPassword";
					$lm = "sambaLMPassword";
					}
				else {
					$nt = "ntPassword";
					$lm = "lmPassword";
					}
				$opts = "UD";
				}
			elsif ($in{'passmode'} == 2) {
				if (!$wassamba || $in{'encpass'} ne $oldpass) {
					# We cannot get back the original unix
					# password, so disable Samba account
					$nt = $xes;
					$lm = $xes;
					$opts = "UD";
					}
				else {
					# Nothing has changed
					}
				}
			elsif ($in{'passmode'} == 3) {
				# Plain-text password to convert
				($nt, $lm) = &samba_password($in{'pass'});
				$opts = "U";
				}
			if ($nt) {
				# Set encrypted passwords if changed
				&samba_password_props($nt, $lm);
				}

			if (!$wassamba) {
				# Add other samba-related options
				push(@props, "ntuid", $user)
					if ($schema->attribute("ntuid") && $config{'samba_class'} eq 'sambaAccount');
				push(@props, "rid", $uid*2+1000)
					if ($schema->attribute("rid") && $config{'samba_class'} eq 'sambaAccount');
				push(@props, "acctFlags",
				     sprintf("[%-11s]", $opts))
					if ($opts &&
					    $schema->attribute("acctFlags") && $config{'samba_class'} eq 'sambaAccount');

				push(@props, "sambaSID", "$config{'samba_domain'}-".($uid*2+1000))
					if ($schema->attribute("sambaSID") && $config{'samba_class'} eq 'sambaSamAccount');
				push(@props, "sambaAcctFlags",
				     sprintf("[%-11s]", $opts))
					if ($opts &&
					    $schema->attribute("sambaAcctFlags") && $config{'samba_class'} eq 'sambaSamAccount');
				# Add extra properties for samba users
				push(@props,
				     &split_props($config{'samba_props'}));
				}
			}
		elsif ($wassamba) {
			# Is no longer a samba user .. take away standard
			# samba properties
			push(@rprops, "ntPassword", "lmPassword",
				      "ntuid", "rid", "acctFlags")
				if($config{'samba_class'} eq 'sambaAccount');
			push(@rprops, "sambaNTPassword", "sambaLMPassword",
				      "sambaSID","sambaAcctFlags")
				if($config{'samba_class'} eq 'sambaSamAccount');

			# Take away any extra properties specified for
			# Samba users
			push(@rprops, &split_first($config{'samba_props'}));
			}

		# Work out imap-related property changes
		if ($in{'cyrus'}) {
			&mail_props();
			}
		if ($in{'cyrus'} && !$wascyrus) {
			# Add any extra properties for IMAP users
			push(@props, &split_props($config{'imap_props'}));
			}
		elsif (!$in{'cyrus'} && $wascyrus) {
			# Take away properties for IMAP users
			push(@rprops, &split_first($config{'imap_props'}));
			&delete_mail_props();
			}

		# Add or update any extra LDAP fields
		&extra_fields();

		# Add or update shadow LDAP fields
		$shadow = &shadow_fields();

		# Update the ldap database
		if ($in{'samba'}) {
			push(@classes, $samba_class);
			}
		else {
			@classes = grep { $_ ne $samba_class } @classes;
			}
		if ($in{'cyrus'}) {
			push(@classes, $cyrus_class);
			}
		else {
			@classes = grep { $_ ne $cyrus_class } @classes;
			}
		push(@classes, "shadowAccount") if ($shadow);
		@rprops = grep { defined($uinfo->get_value($_)) } @rprops;
		$newdn = $in{'dn'};
		&name_fields();
		$rv = $ldap->modify($in{'dn'}, replace =>
                        { "cn" => $real,
			  "uid" => $user,
			  "uidNumber" => $uid,
			  "loginShell" => $shell,
			  "homeDirectory" => $home,
			  "gidNumber" => $gid,
			  "userPassword" => $pass,
			  "objectClass" => [ &unique(@classes) ],
			  @props },
			'delete' => \@rprops);
		if ($rv->code) {
			&error(&text('usave_emod', $rv->error));
			}

		if ($olduser ne $user) {
			# Need to rename the LDAP dn itself
			$renaming = 1;
			$base = &get_user_base();
			$newdn = "uid=$user,$base";
			$rv = $ldap->moddn($in{'dn'}, newrdn => "uid=$user");
			if ($rv->code) {
				&error(&text('usave_emoddn', $rv->error));
				}

			# Check if an addressbook dn exists
			local $olda =
				"ou=$olduser, $config{'addressbook'}";
			$rv = $ldap->search(base => $olda,
					    scope => 'base',
					    filter => '(&(objectClass=organizationalUnit))');
			($oldbook) = $rv->all_entries;

			if ($oldbook) {
				# Need to rename the addressbook dn
				$rv = $ldap->modify($olda, replace =>
					{ "ou" => $user });
				if ($rv->code) {
					&error(&text('usave_emodbook',
						     $rv->error));
					}

				$rv = $ldap->moddn($olda, newrdn =>
					"ou=$user");
				if ($rv->code) {
					&error(&text('usave_emodbookdn',
						     $rv->error));
					}
				}
			}

		if ($in{'cyrus'} && !$wascyrus) {
			if ($config{'addressbook'}) {
                        	# Create addressbook entry
				&setup_addressbook();
				}

			# Setup the imap account as well
			&setup_imap();
			}
		elsif (!$in{'cyrus'} && $wascyrus) {
			if ($config{'addressbook'}) {
                        	# Delete addressbook entry
				&delete_addressbook();
				}
			}
		}

	# Update any groups that the user has been added to/removed from
	foreach $gname (split(/\0/, $in{'sgid'})) {
		$ingroup{$gname}++;
		}
	$base = &get_group_base();
	$rv = $ldap->search(base => $base,
			    filter => '(&(objectClass=posixGroup))');
	foreach $g ($rv->all_entries) {
		local @mems = $g->get_value("memberUid");
		local $gname = $g->get_value("cn");
		if ($renaming) {
			local $idx = &indexof($olduser, @mems);
			if ($ingroup{$gname} && $idx<0) {
				# Need to add to the group
				push(@mems, $user);
				}
			elsif (!$ingroup{$gname} && $idx>=0) {
				# Need to remove from the group
				splice(@mems, $idx, 1);
				}
			elsif ($idx >= 0) {
				# Need to rename in group
				$mems[$idx] = $user;
				}
			else { next; }
			}
		else {
			local $idx = &indexof($user, @mems);
			if ($ingroup{$gname} && $idx<0) {
				# Need to add to the group
				push(@mems, $user);
				}
			elsif (!$ingroup{$gname} && $idx>=0) {
				# Need to remove from the group
				splice(@mems, $idx, 1);
				}
			else { next; }
			}

		# Actually change the group
		$rv = $ldap->modify($g->dn(), replace =>
			{ 'memberUid' => \@mems });
		if ($rv->code) {
			&error(&text('usave_emodgroup', $g->get_value('cn'),
				     $rv->error));
			}
		}

	# Run other modules' scripts
	if ($in{'others'}) {
		$rv = $ldap->search(base => $newdn,
				    scope => 'base',
				    filter => '(&(objectClass=posixAccount))');
		($uinfo) = $rv->all_entries;
		%user = &dn_to_hash($uinfo);
		$user{'passmode'} = $in{'passmode'};
		if ($in{'passmode'} == 2 && $user{'pass'} eq $ouser{'pass'}) {
			# not changing password
			$user{'passmode'} = 4;
			}
		$user{'plainpass'} = $in{'pass'} if ($in{'passmode'} == 3);
		if (!$in{'new'}) {
			$user{'olduser'} = $ouser{'user'};
			&useradmin::other_modules("useradmin_modify_user",
						  \%user, \%ouser);
			}
		else {
			&useradmin::other_modules("useradmin_create_user",
						  \%user);
			}
		}
	}
$ldap->unbind();
delete($in{'pass'});
delete($in{'passmode'});
&webmin_log(!$in{'new'} ? 'modify' : 'create', 'user', $user, \%in);
&redirect("");

# split_props(text, &user)
sub split_props
{
local @rv;
foreach $p (split(/\t+/, &substitute_template($_[0], $_[1]))) {
	if ($p =~ /^(\S+):\s*(.*)/) {
		push(@rv, $1, $2);
		}
	}
return @rv;
}

# substitute_template(text, &hash)
# Given some text and a hash reference, for each ocurrance of $FOO or ${FOO} in
# the text replaces it with the value of the hash key foo
sub substitute_template
{
# Add some extra fixed parameters to the hash
local %hash = %{$_[1]};
$hash{'hostname'} = &get_system_hostname();

# Actually do the substition
local $rv = $_[0];
local $s;
foreach $s (keys %hash) {
	local $us = uc($s);
	local $sv = $hash{$s};
	$rv =~ s/\$\{\Q$us\E\}/$sv/g;
	$rv =~ s/\$\Q$us\E/$sv/g;
	if ($sv) {
		$rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*)\$\{ENDIF-\Q$us\E\}(\n?)/\2/g;
		$rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*)\$\{ENDIF-\Q$us\E\}(\n?)/\2/g;

		$rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*)\$ELSE-\Q$us\E(\n?)([\000-\377]*)\$ENDIF-\Q$us\E(\n?)/\2/g;
		$rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*)\$ENDIF-\Q$us\E(\n?)/\2/g;
		}
	else {
		$rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*)\$\{ELSE-\Q$us\E\}(\n?)([\000-\377]*)\$\{ENDIF-\Q$us\E\}(\n?)/\4/g;
		$rv =~ s/\$\{IF-\Q$us\E\}(\n?)([\000-\377]*)\$\{ENDIF-\Q$us\E\}(\n?)//g;

		$rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*)\$ELSE-\Q$us\E(\n?)([\000-\377]*)\$ENDIF-\Q$us\E(\n?)/\4/g;
		$rv =~ s/\$IF-\Q$us\E(\n?)([\000-\377]*)\$ENDIF-\Q$us\E(\n?)//g;
		}
	}
return $rv;
}

sub split_first
{
local @rv;
foreach $p (split(/\t+/, $_[0])) {
	if ($p =~ /^(\S+):\s*(.*)/) {
		push(@rv, $1);
		}
	}
return @rv;
}

# imap_error(text, &rv)
sub imap_error
{
&error_setup(undef);
&error(&text('usave_eimap', $_[0], $_[1]->{'Text'}));
}

# mail_props()
# Add properties for mail and aliases
sub mail_props
{
# Do nothing if no domain is set
return if (!$config{'domain'});

# Add surname and first name details
local ($firstname, $surname);
if ($in{'real'} =~ /(\S+)\s+(\S+)$/) {
	$firstname = lc($1);
	$surname = lc($2);
	}
elsif ($in{'real'} =~ /(\S+)/) {
	$firstname = lc($1);
	}
else {
	$firstname = lc($in{'user'});
	}
push(@props, "name", $firstname);
if ($surname) {
	push(@props, "mail", "$firstname.$surname\@$config{'domain'}");
	push(@props, "surname", $surname);
	}
else {
	push(@props, "mail", "$firstname\@$config{'domain'}");
	push(@rprops, "surname");
	}

# Add extra aliases
local @alias = split(/\s+/, $in{'alias'});
if ($in{'alias'}) {
	push(@props, "alias", \@alias);
	}
else {
	push(@rprops, "alias");
	}
}

# delete_mail_props()
# Take away any extra properties added by mail_props
sub delete_mail_props
{
push(@rprops, "alias");
push(@rprops, "name");
push(@rprops, "mail");
push(@rprops, "surname");
}

sub setup_imap
{
# Disconnect to save the changes
$ldap->unbind();
undef($ldap);

# Check if the user already exists
$imap = &imap_connect();
$rv = $imap->status("user.".$user, "messages");
if ($rv->{'Status'} eq 'ok') {
	# Already exists, so do nothing
	$imap->logout();
	}
else {
	# Create the user on the IMAP server
	$rv = $imap->create("user.".$user);
	$rv->{'Status'} eq 'ok' ||
		&imap_error($text{'usave_eicreate'}, $rv);

	# Grant all rights to admin user
	$rv = $imap->setacl("user.".$user,
			    $config{'imap_login'}, "lrswipcda");
	$rv->{'Status'} eq 'ok' ||
		&imap_error($text{'usave_eiacl'}, $rv);

	if ($config{'quota'}) {
		# Set his IMAP quota
		$rv = $imap->setquota("user.".$user,
				      "STORAGE", $config{'quota'});
		$rv->{'Status'} eq 'ok' ||
			&imap_error($text{'usave_eiquota'}, $rv);
		}

	# Subscribe the user to his inbox by logging in
	# as him
	$imap->logout();
	$uimap = new Net::IMAP($config{'imap_host'});
	$rv = $uimap->login($user,
		$in{'passmode'} == 0 ? "" : $in{'pass'});
	$rv->{'Status'} eq 'ok' ||
		&imap_error($text{'usave_eilogin'}, $rv);
	$rv = $uimap->subscribe("INBOX");
	$rv->{'Status'} eq 'ok' ||
		&imap_error(&text('usave_eisub', "INBOX"), $rv);

	foreach $f (split(/\s+/, $config{'imap_folders'})) {
		$rv = $uimap->create("user.$user.$f");
		$rv->{'Status'} eq 'ok' ||
		    &imap_error(&text('usave_eifolder',$f),$rv);
		$rv = $uimap->subscribe("user.$user.$f");
		$rv->{'Status'} eq 'ok' ||
		    &imap_error(&text('usave_eisub', $f), $rv);
		}

	$uimap->logout();
	}

# Re-connect for later LDAP operations
$ldap = &ldap_connect();
}

sub setup_addressbook
{
$rv = $ldap->add("ou=$user, $config{'addressbook'}", attr =>
		 [ "ou" => $user,
		   "objectClass" => [ "top", "organizationalUnit" ]
		 ] );
if ($rv->code) {
	&error(&text('usave_ebook',$rv->error));
	}
}

sub delete_addressbook
{
$rv = $ldap->delete("ou=$user, $config{'addressbook'}");
}

# dn_to_hash(&ldap-object)
sub dn_to_hash
{
local %user = ( 'user' => $_[0]->get_value("uid"),
		'uid' => $_[0]->get_value("uidNumber"),
		'gid' => $_[0]->get_value("gidNumber"),
		'real' => $_[0]->get_value("cn"),
		'home' => $_[0]->get_value("homeDirectory"),
		'shell' => $_[0]->get_value("loginShell"),
		'pass' => $_[0]->get_value("userPassword") );
$user{'pass'} =~ s/^{[a-z0-9]+}//i;
return %user;
}

sub extra_fields
{
local @fields = map { [ split(/\s+/, $_, 2) ] } split(/\t/, $config{'fields'});
local $i = 0;
foreach $f (@fields) {
	if ($in{"field_$i"} eq "") {
		push(@rprops, $f->[0]);
		}
	else {
		push(@props, $f->[0], $in{"field_$i"});
		}
	$i++;
	}
}

# samba_password_props(nt, nl)
sub samba_password_props
{
	if($config{'samba_class'} eq 'sambaSamAccount')
	{
		push(@props, "sambaNTPassword", $_[0])
			if ($schema->attribute("sambaNTPassword"));
		push(@props, "sambaLMPassword", $_[1])
			if ($schema->attribute("sambaLMPassword"));
	}
	else
	{
		push(@props, "ntPassword", $_[0])
			if ($schema->attribute("ntPassword"));
		push(@props, "lmPassword", $_[1])
			if ($schema->attribute("lmPassword"));
	}
}

sub name_fields
{
if ($config{'given'}) {
	push(@props, "givenName", $firstname)
		if ($firstname && $schema->attribute("givenName"));
	push(@props, "sn", $lastname)
		if ($lastname && $schema->attribute("sn"));
	}
else {
	#push(@props, "sn", $in{'real'})
	#	if ($schema->attribute("sn"));
	}
if ($schema->attribute("gecos")) {
	push(@props, "gecos", $in{'real'});
	}
}

sub shadow_fields
{
if ($schema->attribute("shadowLastChange")) {
	# Validate shadow-password inputs
	$in{'min'} =~ /^[0-9]*$/ ||
		&error(&text('usave_emin', $in{'min'}));
	if ($in{'min'} ne '') {
		push(@props, "shadowMin", $in{'min'});
		}
	else {
		push(@rprops, "shadowMin");
		}
	$in{'max'} =~ /^[0-9]*$/ ||
		&error(&text('usave_emax', $in{'max'}));
	if ($in{'max'} ne '') {
		push(@props, "shadowMax", $in{'max'});
		}
	else {
		push(@rprops, "shadowMax");
		}
	if ($in{'expired'} ne "" && $in{'expirem'} ne ""
	    && $in{'expirey'} ne "") {
		eval { $expire = timelocal(0, 0, 12,
					$in{'expired'},
					$in{'expirem'}-1,
					$in{'expirey'}-1900); };
		if ($@) { &error($text{'usave_eexpire'}); }
		push(@props, "shadowExpire", int($expire / (60*60*24)));
		}
	else {
		push(@rprops, "shadowExpire");
		}
	$in{'warn'} =~ /^[0-9]*$/ ||
	    &error(&text('usave_ewarn', $in{'warn'}));
	if ($in{'warn'} ne '') {
		push(@props, "shadowWarning", $in{'warn'});
		}
	else {
		push(@rprops, "shadowWarning");
		}
	$in{'inactive'} =~ /^[0-9]*$/ ||
	    &error(&text('usave_einactive', $in{'inactive'}));
	if ($in{'inactive'} ne '') {
		push(@props, "shadowInactive", $in{'inactive'});
		}
	else {
		push(@rprops, "shadowInactive");
		}
	if ($in{'passmode'} == 3 ||
	    $in{'passmode'} == 2 && $pass ne $oldpass) {
		$daynow = int(time() / (60*60*24));
		push(@props, "shadowLastChange", $daynow);
		}
	return 1;
	}
else {
	return 0;
	}
}

